diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index ec60018..9985810 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -10,9 +10,16 @@ on: - develop jobs: + build: + uses: mailchimp/wordpress/.github/workflows/build-release-zip.yml@develop + cypress: + needs: build name: ${{ matrix.core.name }} runs-on: ubuntu-latest + env: + CYPRESS_MAILCHIMP_USERNAME: ${{ secrets.MAILCHIMP_USERNAME }} + CYPRESS_MAILCHIMP_PASSWORD: ${{ secrets.MAILCHIMP_PASSWORD }} strategy: matrix: core: @@ -24,6 +31,16 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Download build zip + uses: actions/download-artifact@v4 + with: + name: ${{ github.event.repository.name }} + path: ${{ github.event.repository.name }} + + - name: Display structure of downloaded files + run: ls -R + working-directory: ${{ github.event.repository.name }} + - uses: actions/setup-node@v4 with: node-version-file: '.nvmrc' @@ -31,8 +48,8 @@ jobs: - name: Install dependencies run: npm ci - - name: Set the core version - run: ./tests/bin/set-core-version.js ${{ matrix.core.version }} + - name: Set the core version and plugins config + run: ./tests/bin/set-core-version.js --core=${{ matrix.core.version }} --plugins=./${{ github.event.repository.name }} - name: Set up WP environment run: npm run env:start @@ -41,6 +58,7 @@ jobs: run: npm run cypress:run - name: Update summary + if: always() run: | npx mochawesome-merge ./tests/cypress/reports/*.json -o tests/cypress/reports/mochawesome.json rm -rf ./tests/cypress/reports/mochawesome-*.json diff --git a/README.md b/README.md index 41e20b0..35fa33e 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,23 @@ Currently we have the plugin configured so it can be translated and the followin * sv_SE - Swedish in Sweden (thanks to [Sebastian Johnsson](http://www.agiley.se/) for contributing) * tr_TR - Turkish in Turkey (thanks to [Hakan E.](http://kazancexpert.com/) for contributing) +## E2E tests +The `tests` directory contains end-to-end tests for the project, utilizing Cypress to run tests in an environment created using wp-env. + +### Pre-requisites +- Node.js v20 +- Docker +- Create an account in [Mailchimp](https://mailchimp.com/) + +### Run E2E tests in local +1. Run `npm install`. +2. Run `npm run build`. +3. Run `npm run env:start`. +4. Set Mailchimp credentials as environment variables: + - run `export CYPRESS_MAILCHIMP_USERNAME="your mailchimp username"` + - run `export CYPRESS_MAILCHIMP_PASSWORD="your mailchimp password"` +5. Run `npm run cypress:run`. You can also run `npm run cypress:open` to run tests in UI mode. + ## Support Level **Active:** Mailchimp is actively working on this, and we expect to continue work for the foreseeable future including keeping tested up to the most recent version of WordPress. Bug reports, feature requests, questions, and pull requests are welcome. diff --git a/package-lock.json b/package-lock.json index a821dba..57bc189 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,10 @@ "@wordpress/server-side-render": "^5.2.0" }, "devDependencies": { - "@10up/cypress-wp-utils": "^0.3.0", + "@10up/cypress-wp-utils": "^0.4.0", "@wordpress/env": "^10.2.0", "10up-toolkit": "^6.2.0", - "cypress": "^13.12.0", + "cypress": "^13.13.2", "cypress-mochawesome-reporter": "^3.8.2", "mochawesome-json-to-md": "^1.3.5" } @@ -42,9 +42,9 @@ } }, "node_modules/@10up/cypress-wp-utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@10up/cypress-wp-utils/-/cypress-wp-utils-0.3.0.tgz", - "integrity": "sha512-iMjvca50TerMCY9M9vL0FIE+80ye5YohaQp3XvhgUgQdc4LS51X2fH+lhdb0uRmBTiUQcISazEvWGJxV7DeTbw==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@10up/cypress-wp-utils/-/cypress-wp-utils-0.4.0.tgz", + "integrity": "sha512-7cNELIX6ml5V9JEU83iEyQ6dkZ77ImdR5HKjUP4oyArQogPVcFPUnokU7GInH8DicqXbESrrkxZ0IfnNtNWh+A==", "dev": true, "engines": { "node": ">=12.0" @@ -9912,13 +9912,13 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/cypress": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.12.0.tgz", - "integrity": "sha512-udzS2JilmI9ApO/UuqurEwOvThclin5ntz7K0BtnHBs+tg2Bl9QShLISXpSEMDv/u8b6mqdoAdyKeZiSqKWL8g==", + "version": "13.13.2", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.13.2.tgz", + "integrity": "sha512-PvJQU33933NvS1StfzEb8/mu2kMy4dABwCF+yd5Bi7Qly1HOVf+Bufrygee/tlmty/6j5lX+KIi8j9Q3JUMbhA==", "dev": true, "hasInstallScript": true, "dependencies": { - "@cypress/request": "^3.0.0", + "@cypress/request": "^3.0.1", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", @@ -9957,7 +9957,7 @@ "request-progress": "^3.0.0", "semver": "^7.5.3", "supports-color": "^8.1.1", - "tmp": "~0.2.1", + "tmp": "~0.2.3", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, diff --git a/package.json b/package.json index 193021c..2c3fd71 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ }, "scripts": { "cypress:open": "cypress open --config-file tests/cypress/config.js --e2e --browser chrome", - "cypress:run": "cypress run --config-file tests/cypress/config.js", + "cypress:run": "cypress run --config-file tests/cypress/config.js --e2e --browser chrome", "env": "wp-env", "env:start": "wp-env start", "env:stop": "wp-env stop", @@ -31,10 +31,10 @@ "build": "10up-toolkit build" }, "devDependencies": { - "@10up/cypress-wp-utils": "^0.3.0", + "@10up/cypress-wp-utils": "^0.4.0", "@wordpress/env": "^10.2.0", "10up-toolkit": "^6.2.0", - "cypress": "^13.12.0", + "cypress": "^13.13.2", "cypress-mochawesome-reporter": "^3.8.2", "mochawesome-json-to-md": "^1.3.5" }, diff --git a/tests/bin/set-core-version.js b/tests/bin/set-core-version.js index f320701..abe61c4 100755 --- a/tests/bin/set-core-version.js +++ b/tests/bin/set-core-version.js @@ -1,34 +1,46 @@ #!/usr/bin/env node const fs = require( 'fs' ); -const { exit } = require( 'process' ); -const path = `${ process.cwd() }/.wp-env.override.json`; +const path = `${ process.cwd() }/.wp-env.json`; -// eslint-disable-next-line import/no-dynamic-require -const config = fs.existsSync( path ) ? require( path ) : {}; +let config = fs.existsSync( path ) ? require( path ) : { plugins: [ '.' ] }; -const args = process.argv.slice( 2 ); +const args = {}; +process.argv + .slice(2, process.argv.length) + .forEach( arg => { + if (arg.slice(0,2) === '--') { + const param = arg.split('='); + const paramName = param[0].slice(2,param[0].length); + const paramValue = param.length > 1 ? param[1] : true; + args[paramName] = paramValue; + } + }); -if ( args.length === 0 ) exit( 0 ); +if ( ! args.core && ! args.plugins ) { + return; +} + +if ( 'latest' === args.core ) { + delete args.core; +} -if ( args[ 0 ] === 'latest' ) { - if ( fs.existsSync( path ) ) { - fs.unlinkSync( path ); - } - exit( 0 ); +if( Object.keys(args).length === 0 ) { + return; } -config.core = args[ 0 ]; +if ( args.plugins ) { + args.plugins = args.plugins.split(','); +} -// eslint-disable-next-line no-useless-escape -if ( ! config.core.match( /^WordPress\/WordPress\#/ ) ) { - config.core = `WordPress/WordPress#${ config.core }`; +config = { + ...config, + ...args, } try { - fs.writeFileSync( path, JSON.stringify( config ) ); + fs.writeFileSync( path, JSON.stringify( config ) ); } catch ( err ) { - // eslint-disable-next-line no-console - console.error( err ); + console.error( err ); } diff --git a/tests/cypress/config.js b/tests/cypress/config.js index b881418..0720819 100644 --- a/tests/cypress/config.js +++ b/tests/cypress/config.js @@ -25,6 +25,10 @@ module.exports = defineConfig( { supportFile: 'tests/cypress/support/index.js', defaultCommandTimeout: 20000, }, + retries: { + runMode: 2, + openMode: 0, + }, } ); /** diff --git a/tests/cypress/e2e/admin.test.js b/tests/cypress/e2e/admin.test.js index 1f9a545..65d5f4a 100644 --- a/tests/cypress/e2e/admin.test.js +++ b/tests/cypress/e2e/admin.test.js @@ -1,10 +1,22 @@ -describe( 'Admin can login and make sure plugin is activated', () => { - before( () => { +/* eslint-disable no-undef */ +describe('Admin can login and make sure plugin is activated', () => { + before(() => { cy.login(); - } ); + }); - it( 'Can deactivate and activate plugin?', () => { - cy.deactivatePlugin( 'mailchimp' ); - cy.activatePlugin( 'mailchimp' ); - } ); -} ); + it('Can deactivate and activate plugin?', () => { + cy.deactivatePlugin('mailchimp'); + cy.activatePlugin('mailchimp'); + }); + + it('Can see "Mailchimp" menu and Can visit "Mailchimp" settings page.', () => { + cy.visit('/wp-admin/'); + + // Check Mailchimp menu. + cy.get('#adminmenu li#toplevel_page_mailchimp_sf_options').contains('Mailchimp'); + + // Check Heading + cy.get('#adminmenu li#toplevel_page_mailchimp_sf_options').click(); + cy.get('#wpbody .mailchimp-header h1').contains('Mailchimp List Subscribe Form'); + }); +}); diff --git a/tests/cypress/e2e/connect.test.js b/tests/cypress/e2e/connect.test.js new file mode 100644 index 0000000..89c71bf --- /dev/null +++ b/tests/cypress/e2e/connect.test.js @@ -0,0 +1,43 @@ +/* eslint-disable no-undef */ +describe('Admin can connect to "Mailchimp" Account', () => { + before(() => { + cy.login(); + }); + + it('Can connect to "Mailchimp" using OAuth flow.', () => { + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + + // Logout if already connected. + cy.get('body').then(($body) => { + if ($body.find('input[value="Logout"]').length > 0) { + cy.get('input[value="Logout"]').click(); + } + }); + + // Check Mailchimp menu. + cy.get('#mailchimp_sf_oauth_connect').should('exist'); + + // Enable popup capture. + cy.capturePopup(); + + cy.get('#mailchimp_sf_oauth_connect').click(); + cy.wait(6000); + + cy.popup() + .find('input#username') + .clear() + .type(Cypress.env('MAILCHIMP_USERNAME'), { force: true }); + cy.popup() + .find('input#password') + .clear() + .type(Cypress.env('MAILCHIMP_PASSWORD'), { force: true }); + cy.popup().find('button[type="submit"]').click({ force: true }); + cy.wait(10000); // Not a best practice, but did not find a better way to handle this. + + cy.popup().find('input#submitButton').click({ force: true }); + cy.wait(10000); // Not a best practice, but did not find a better way to handle this. + + cy.get('.mc-user h3').contains('Logged in as: '); + cy.get('input[value="Logout"]').should('exist'); + }); +}); diff --git a/tests/cypress/e2e/settings.test.js b/tests/cypress/e2e/settings.test.js new file mode 100644 index 0000000..ceb4406 --- /dev/null +++ b/tests/cypress/e2e/settings.test.js @@ -0,0 +1,205 @@ +/* eslint-disable no-undef */ +describe('Admin can update plugin settings', () => { + let shortcodePostURL = '/mailchimp-signup-form-shortcode'; + let blockPostPostURL = '/mailchimp-signup-form-block'; + + before(() => { + cy.login(); + }); + + it('Admin can see list and save it', () => { + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + + cy.get('.mc-h2').contains('Your Lists'); + cy.get('#mc_list_id').select('10up'); + cy.get('input[value="Update List"]').click(); + cy.get('#mc-message .success_msg b').contains('Success!'); + }); + + it('Admin can create a Signup form using the shortcode', () => { + const postTitle = 'Mailchimp signup form - shortcode'; + const beforeSave = () => { + cy.insertBlock('core/shortcode').then((id) => { + cy.getBlockEditor() + .find(`#${id} .blocks-shortcode__textarea`) + .clear() + .type('[mailchimpsf_form]'); + }); + }; + cy.createPost({ title: postTitle, content: '', beforeSave }).then((post) => { + if (post) { + shortcodePostURL = `/?p=${post.id}`; + cy.visit(shortcodePostURL); + cy.get('#mc_signup').should('exist'); + cy.get('#mc_mv_EMAIL').should('exist'); + cy.get('#mc_signup_submit').should('exist'); + cy.get('#mc_signup_submit').click(); + cy.get('.mc_error_msg').should('exist'); + cy.get('.mc_error_msg').contains(': This value should not be blank.'); + } + }); + }); + + it('Admin can create a Signup form using Mailchimp block', () => { + const postTitle = 'Mailchimp signup form - Block'; + const beforeSave = () => { + cy.insertBlock('mailchimp/mailchimp', 'Mailchimp List Subscribe Form'); + cy.wait(500); + }; + cy.createPost({ title: postTitle, content: '', beforeSave }).then((postBlock) => { + if (postBlock) { + blockPostPostURL = `/?p=${postBlock.id}`; + cy.visit(blockPostPostURL); + cy.get('#mc_signup').should('exist'); + cy.get('#mc_mv_EMAIL').should('exist'); + cy.get('#mc_signup_submit').should('exist'); + cy.get('#mc_signup_submit').click(); + cy.get('.mc_error_msg').should('exist'); + cy.get('.mc_error_msg').contains(': This value should not be blank.'); + } + }); + }); + + it('Admin can set content options for signup form', () => { + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + + // Set content options + const header = 'Subscribe to our newsletter'; + const subHeader = + 'Join our mailing list to receive the latest news and updates from our team.'; + const button = 'Subscribe Now'; + cy.get('#mc_header_content').clear().type(header); + cy.get('#mc_subheader_content').clear().type(subHeader); + cy.get('#mc_submit_text').clear().type(button); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify content options + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('.mc_custom_border_hdr').contains(header); + cy.get('#mc_subheader').contains(subHeader); + cy.get('#mc_signup_submit').contains(button); + }); + }); + + it('Admin can remove mailchimp CSS', () => { + // Remove mailchimp CSS. + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_nuke_all_styles').check(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_subheader').should('not.have.css', 'margin-bottom', '18px'); + }); + + // Enable mailchimp CSS. + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_nuke_all_styles').uncheck(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_subheader').should('have.css', 'margin-bottom', '18px'); + }); + }); + + it('Admin can set custom styling on signup form', () => { + // Enable custom styling and set values. + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_custom_style').check(); + cy.get('#mc_form_border_width').clear().type('10'); + cy.get('#mc_form_border_color').clear().type('000000'); + cy.get('#mc_form_text_color').clear().type('FF0000'); + cy.get('#mc_form_background').clear().type('00FF00'); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_signup form').should('have.css', 'border-width', '10px'); + cy.get('#mc_signup form').should('have.css', 'border-color', 'rgb(0, 0, 0)'); + cy.get('#mc_signup form').should('have.css', 'color', 'rgb(255, 0, 0)'); + cy.get('#mc_signup form').should('have.css', 'background-color', 'rgb(0, 255, 0)'); + }); + + // Reset + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_custom_style').uncheck(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + }); + + it('Admin can set Merge Fields Included settings', () => { + // Remove mailchimp CSS. + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_mv_FNAME').uncheck(); + cy.get('#mc_mv_LNAME').uncheck(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_mv_FNAME').should('not.exist'); + cy.get('#mc_mv_LNAME').should('not.exist'); + }); + + // Reset + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_mv_FNAME').check(); + cy.get('#mc_mv_LNAME').check(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_mv_FNAME').should('exist'); + cy.get('#mc_mv_LNAME').should('exist'); + }); + }); + + it('Admin can set list options settings', () => { + // Remove mailchimp CSS. + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_use_javascript').uncheck(); + cy.get('#mc_use_datepicker').uncheck(); + cy.get('#mc_use_unsub_link').check(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + // Verify + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_submit_type').should('have.value', 'html'); + cy.get('#mc_mv_BIRTHDAY').should('not.have.class', 'hasDatepicker'); + cy.get('#mc_mv_BIRTHDAY').click(); + cy.get('#ui-datepicker-div').should('not.exist'); + cy.get('#mc_unsub_link').should('exist'); + }); + + // Reset + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mc_use_javascript').check(); + cy.get('#mc_use_datepicker').check(); + cy.get('#mc_use_unsub_link').uncheck(); + cy.get('input[value="Update Subscribe Form Settings"]').first().click(); + + [shortcodePostURL, blockPostPostURL].forEach((url) => { + cy.visit(url); + cy.get('#mc_submit_type').should('have.value', 'js'); + cy.get('#mc_mv_BIRTHDAY').should('have.class', 'hasDatepicker'); + cy.get('#mc_mv_BIRTHDAY').click(); + cy.get('#ui-datepicker-div').should('exist'); + cy.get('#mc_unsub_link').should('not.exist'); + }); + }); + + it('Admin can logout', () => { + cy.visit('/wp-admin/admin.php?page=mailchimp_sf_options'); + cy.get('#mailchimp_sf_oauth_connect').should('not.exist'); + cy.get('input[value="Logout"]').click(); + + // connect to "Mailchimp" Account button should be visible. + cy.get('#mailchimp_sf_oauth_connect').should('exist'); + }); +}); diff --git a/tests/cypress/support/commands.js b/tests/cypress/support/commands.js index 119ab03..8d89823 100644 --- a/tests/cypress/support/commands.js +++ b/tests/cypress/support/commands.js @@ -1,3 +1,4 @@ +/* eslint-disable no-undef */ // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite @@ -23,3 +24,27 @@ // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) + +const state = {}; + +/** + * Intercepts calls to window.open() to keep a reference to the new window + */ +Cypress.Commands.add('capturePopup', () => { + cy.window().then((win) => { + const { open } = win; + cy.stub(win, 'open').callsFake((...params) => { + // Capture the reference to the popup + state.popup = open(...params); + return state.popup; + }); + }); +}); + +/** + * Returns a wrapped body of a captured popup + */ +Cypress.Commands.add('popup', () => { + const popup = Cypress.$(state.popup.document); + return cy.wrap(popup.contents().find('body')); +}); diff --git a/tests/cypress/support/index.js b/tests/cypress/support/index.js index d8895b6..5652037 100644 --- a/tests/cypress/support/index.js +++ b/tests/cypress/support/index.js @@ -15,6 +15,9 @@ import '@10up/cypress-wp-utils'; +// Import commands.js using ES2015 syntax: +import './commands'; + beforeEach( () => { cy.session( 'login', cy.login, { cacheAcrossSpecs: true,