diff --git a/.dev/tests/playwright/global-setup.js b/.dev/tests/playwright/global-setup.js new file mode 100644 index 00000000000..cff12ba3696 --- /dev/null +++ b/.dev/tests/playwright/global-setup.js @@ -0,0 +1,42 @@ +/** + * Global setup file for Playwright tests, and is executed once before all tests + * are run. + */ +const { chromium, expect } = require( '@playwright/test' ); + +module.exports = async () => { + const browser = await chromium.launch(); + const context = await browser.newContext( { + baseURL: 'http://localhost:8889', + recordVideo: { + dir: '.dev/tests/playwright/videos', + }, + } ); + + const page = await context.newPage(); + + // Log in to WordPress. + await page.goto( '/wp-login.php' ); + await page.fill( '#user_login', 'admin' ); + await page.fill( '#user_pass', 'password' ); + await page.click( '#wp-submit' ); + await page.goto( '/wp-admin/post-new.php?post_type=post' ); + + // Wait for wp.data to be available. + await page.waitForFunction( () => window.wp.data !== undefined ); + + // Ensure the Welcome Guide is disabled. + if ( await page.evaluate( `wp.data.select( 'core/edit-post' ).isFeatureActive( 'welcomeGuide' )` ) ) { + await page.evaluate( `wp.data.dispatch( 'core/edit-post' ).toggleFeature( 'welcomeGuide' )` ); + } + + await page.reload(); + + // Expect the Welcome Guide to be hidden. + await expect( page.locator( '.edit-post-welcome-guide' ) ).toBeHidden(); + + // Save signed-in state to 'storageState.json'. + await page.context().storageState( { path: '.dev/tests/playwright/storage-state.json' } ); + await context.close(); + await browser.close(); +}; diff --git a/.gitignore b/.gitignore index 6d14888dde0..3e40ce30797 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,8 @@ languages/*.po cypress.env.json .wp-env.json .wp-env.override.json +/test-results/ +/playwright-report/ +/playwright/.cache/ +.dev/tests/playwright/videos/ +.dev/tests/playwright/storage-state.json diff --git a/package.json b/package.json index 9768ee232ca..381bdc4f484 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "@godaddy-wordpress/eslint-config": "^0.6.0", "@godaddy-wordpress/stylelint-config": "^0.6.0", "@jest/core": "^27.4.5", + "@playwright/test": "^1.29.2", "@react-google-maps/api": "^2.7.0", "@testing-library/jest-dom": "^5.16.1", "@testing-library/react": "^12.1.5", diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 00000000000..476ffe896dd --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,56 @@ +// @ts-check +const { devices } = require( '@playwright/test' ); + +const config = { + expect: { + timeout: 5000, + }, + forbidOnly: !! process.env.CI, + fullyParallel: true, + globalSetup: require.resolve( './.dev/tests/playwright/global-setup' ), + outputDir: './.dev/tests/playwright/test-results/', + projects: [ + { + name: 'chromium', + use: { + ...devices[ 'Desktop Chrome' ], + }, + }, + + { + name: 'firefox', + use: { + ...devices[ 'Desktop Firefox' ], + }, + }, + + /** + * WordPress version 6.1+ throws and error with the version of Webkit + * that Playwright currently utilizes. Likely this will be resolved when + * Gutenberg finishes migrating to Playwright, and then we can use the + * wordpress/e2e-test-utils-playwright package from Core. + */ + // { + // name: 'webkit', + // use: { + // ...devices[ 'Desktop Safari' ], + // }, + // }, + ], + + reporter: process.env.CI ? 'dot' : [ [ 'html', { open: 'never' } ] ], + retries: process.env.CI ? 2 : 0, + testDir: './src', + testMatch: '**/src/**/**/*.playwright.js', + timeout: 30 * 1000, + use: { + actionTimeout: 0, + baseURL: 'http://localhost:8889', + headless: true, + storageState: './.dev/tests/playwright/storage-state.json', + trace: 'on-first-retry', + video: 'on-first-retry', + }, +}; + +module.exports = config; diff --git a/src/blocks/accordion/test/accordion.cypress.js b/src/blocks/accordion/test/accordion.cypress.js index ecbcbe4ed94..e10574efc96 100644 --- a/src/blocks/accordion/test/accordion.cypress.js +++ b/src/blocks/accordion/test/accordion.cypress.js @@ -5,6 +5,14 @@ import * as helpers from '../../../../.dev/tests/cypress/helpers'; describe( 'Block: Accordion', () => { beforeEach( () => { + cy.visit( Cypress.env( 'testURL' ) + '/wp-admin/post-new.php?post_type=post', { + onLoad: ( contentWindow ) => { + if ( !! contentWindow.wp.data.select( 'core/edit-post' ).isFeatureActive( 'welcomeGuide' ) ) { + contentWindow.wp.data.dispatch( 'core/edit-post' ).toggleFeature( 'welcomeGuide' ); + } + }, + } ); + helpers.addBlockToPost( 'coblocks/accordion', true ); } ); diff --git a/src/blocks/accordion/test/accordion.playwright.js b/src/blocks/accordion/test/accordion.playwright.js new file mode 100644 index 00000000000..2787b9efbb3 --- /dev/null +++ b/src/blocks/accordion/test/accordion.playwright.js @@ -0,0 +1,74 @@ +import { test, expect } from '@playwright/test'; + +const blockName = 'coblocks/accordion'; + +test.describe.parallel( 'Accordion', () => { + test.beforeEach( async ( { page, browserName }, info ) => { + await page.goto( '/wp-admin/post-new.php?post_type=post' ); + + await page.waitForFunction( () => window.wp.data !== undefined ); + + await page.evaluate( `wp.data.dispatch( 'core/editor' ).editPost( { title: '${ browserName } - ${ info.title }' } )` ); + await page.evaluate( `wp.data.dispatch( 'core/block-editor' ).insertBlock( wp.blocks.createBlock( '${ blockName }' ) )` ); + await page.evaluate( `wp.data.dispatch( 'core/editor' ).savePost()` ); + } ); + + test( 'saves with no title or content', async ( { page } ) => { + await expect( page.locator( `[data-type="${ blockName }"]` ) ).toBeVisible(); + await page.reload(); + await expect( page.locator( `[data-type="${ blockName }"]` ) ).toBeVisible(); + } ); + + test( 'saves with custom title', async ( { page } ) => { + const title = 'GoDaddy'; + const titleLocator = '.wp-block-coblocks-accordion-item__title'; + + await page.type( titleLocator, title ); + + await expect( page.locator( titleLocator ) ).not.toBeEmpty(); + + await expect( page.locator( titleLocator ) ).toContainText( title ); + + await page.evaluate( `wp.data.dispatch( 'core/editor' ).savePost()` ); + await page.reload(); + + await expect( page.locator( titleLocator ) ).toContainText( title ); + } ); + + test( 'saves with custom content', async ( { page } ) => { + const clientId = await page.evaluate( `wp.data.select( 'core/block-editor' ).getBlocks().shift().innerBlocks.shift().clientId` ); + const contentLocator = '.wp-block-coblocks-accordion-item__content .wp-block-paragraph:first-child'; + const contentText = 'Domain Names, Websites, Hosting & Online Marketing Tools'; + + await page.type( '.wp-block-coblocks-accordion-item__title', 'GoDaddy' ); + await page.evaluate( `wp.data.dispatch( 'core/block-editor' ).insertBlocks( wp.blocks.createBlock( 'core/paragraph', { content: '${ contentText }' } ), 0, '${ clientId }' )` ); + + await expect( page.locator( contentLocator ) ).toBeVisible(); + await expect( page.locator( contentLocator ) ).not.toBeEmpty(); + + await expect( page.locator( contentLocator ) ).toContainText( contentText ); + + await page.evaluate( `wp.data.dispatch( 'core/editor' ).savePost()` ); + await page.reload(); + + await page.locator( '.wp-block-coblocks-accordion-item__title' ).focus(); + await expect( page.locator( contentLocator ) ).toContainText( contentText ); + } ); + + test( 'appends additional accordion item', async ( { page } ) => { + const accordionItemsLocator = '.wp-block-coblocks-accordion [data-type="coblocks/accordion-item"]'; + await expect( page.locator( '.coblocks-block-appender' ) ).toBeVisible(); + + await page.locator( '.coblocks-block-appender' ).click(); + + let accordionItems = await page.locator( accordionItemsLocator ); + await expect( accordionItems ).toHaveCount( 2 ); + + await page.evaluate( `wp.data.dispatch( 'core/editor' ).savePost()` ); + await page.reload(); + + accordionItems = await page.locator( accordionItemsLocator ); + await expect( accordionItems ).toHaveCount( 2 ); + } ); +} ); + diff --git a/src/blocks/alert/test/alert.cypress.js b/src/blocks/alert/test/alert.cypress.js index 20089328cc0..79c5d7196bb 100644 --- a/src/blocks/alert/test/alert.cypress.js +++ b/src/blocks/alert/test/alert.cypress.js @@ -4,6 +4,16 @@ import * as helpers from '../../../../.dev/tests/cypress/helpers'; describe( 'Test CoBlocks Alert Block', function() { + beforeEach( () => { + cy.visit( Cypress.env( 'testURL' ) + '/wp-admin/post-new.php?post_type=post', { + onLoad: ( contentWindow ) => { + if ( !! contentWindow.wp.data.select( 'core/edit-post' ).isFeatureActive( 'welcomeGuide' ) ) { + contentWindow.wp.data.dispatch( 'core/edit-post' ).toggleFeature( 'welcomeGuide' ); + } + }, + } ); + } ); + /** * Test that we can add a alert block to the content, not add any text or * alter any settings, and are able to successfully save the block without errors. diff --git a/src/blocks/alert/test/alert.playwright.js b/src/blocks/alert/test/alert.playwright.js new file mode 100644 index 00000000000..1e3e80fcf6c --- /dev/null +++ b/src/blocks/alert/test/alert.playwright.js @@ -0,0 +1,42 @@ +import { test, expect } from '@playwright/test'; + +const blockName = 'coblocks/alert'; + +test.describe.parallel( 'Alert', () => { + test.beforeEach( async ( { page, browserName }, info ) => { + await page.goto( '/wp-admin/post-new.php?post_type=post' ); + + await page.waitForFunction( () => window.wp.data !== undefined ); + + await page.evaluate( `wp.data.dispatch( 'core/editor' ).editPost( { title: '${ browserName } - ${ blockName } - ${ info.title }' } )` ); + await page.evaluate( `wp.data.dispatch( 'core/block-editor' ).insertBlocks( wp.blocks.createBlock( '${ blockName }' ) )` ); + await page.evaluate( `wp.data.dispatch( 'core/editor' ).savePost()` ); + } ); + + test( 'saves with no title or text', async ( { page } ) => { + await expect( page.locator( `[data-type="${ blockName }"]` ) ).toBeVisible(); + await page.reload(); + await expect( page.locator( `[data-type="${ blockName }"]` ) ).toBeVisible(); + } ); + + test( 'saves with custom title and text', async ( { page } ) => { + const alertTitle = 'GoDaddy'; + const alertText = 'Domain Names, Websites, Hosting & Online Marketing Tools'; + + await page.type( '.wp-block-coblocks-alert__title', alertTitle ); + await page.type( '.wp-block-coblocks-alert__text', alertText ); + + await expect( page.locator( `.wp-block-coblocks-alert__title` ) ).not.toBeEmpty(); + await expect( page.locator( `.wp-block-coblocks-alert__text` ) ).not.toBeEmpty(); + + await expect( page.locator( `.wp-block-coblocks-alert__title` ) ).toContainText( alertTitle ); + await expect( page.locator( `.wp-block-coblocks-alert__text` ) ).toContainText( alertText ); + + await page.evaluate( `wp.data.dispatch( 'core/editor' ).savePost()` ); + await page.reload(); + + await expect( page.locator( `.wp-block-coblocks-alert__title` ) ).toContainText( alertTitle ); + await expect( page.locator( `.wp-block-coblocks-alert__text` ) ).toContainText( alertText ); + } ); + +} ); diff --git a/src/blocks/click-to-tweet/test/click-to-tweet.playwright.js b/src/blocks/click-to-tweet/test/click-to-tweet.playwright.js new file mode 100644 index 00000000000..cf6d35437c7 --- /dev/null +++ b/src/blocks/click-to-tweet/test/click-to-tweet.playwright.js @@ -0,0 +1,55 @@ +import { test, expect } from '@playwright/test'; + +const blockName = 'coblocks/click-to-tweet'; + +test.describe.parallel( 'Click to Tweet', () => { + test.beforeEach( async ( { page, browserName }, info ) => { + await page.goto( '/wp-admin/post-new.php?post_type=post' ); + + await page.waitForFunction( () => window.wp.data !== undefined ); + + await page.evaluate( `wp.data.dispatch( 'core/editor' ).editPost( { title: '${ browserName } - ${ info.title }' } )` ); + await page.evaluate( `wp.data.dispatch( 'core/block-editor' ).insertBlock( wp.blocks.createBlock( '${ blockName }' ) )` ); + await page.evaluate( `wp.data.dispatch( 'core/editor' ).savePost()` ); + } ); + + test( 'saves with no content', async ( { page } ) => { + await expect( page.locator( `[data-type="${ blockName }"]` ) ).toBeVisible(); + await page.reload(); + await expect( page.locator( `[data-type="${ blockName }"]` ) ).toBeVisible(); + } ); + + test( 'saves with custom content', async ( { page } ) => { + const text = 'GoDaddy - Domain Names, Websites, Hosting & Online Marketing Tools'; + const textLocator = '.wp-block-coblocks-click-to-tweet__text'; + + await page.type( textLocator, text ); + + await expect( page.locator( textLocator ) ).not.toBeEmpty(); + + await expect( page.locator( textLocator ) ).toContainText( text ); + + await page.evaluate( `wp.data.dispatch( 'core/editor' ).savePost()` ); + await page.reload(); + + await expect( page.locator( textLocator ) ).toContainText( text ); + } ); + + test( 'can add tweet via', async ( { page } ) => { + const via = 'godaddy'; + const viaLocator = '.wp-block-coblocks-click-to-tweet__via'; + + await page.type( viaLocator, via ); + + await expect( page.locator( viaLocator ) ).not.toBeEmpty(); + + await expect( page.locator( viaLocator ) ).toHaveValue( via ); + + await page.evaluate( `wp.data.dispatch( 'core/editor' ).savePost()` ); + await page.reload(); + + await page.locator( '.wp-block-coblocks-click-to-tweet__text' ).focus(); + await expect( page.locator( viaLocator ) ).toHaveValue( via ); + } ); +} ); + diff --git a/yarn.lock b/yarn.lock index afe97eb1bb9..dd8a2bca16e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1583,6 +1583,14 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@playwright/test@^1.29.2": + version "1.29.2" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.29.2.tgz#c48184721d0f0b7627a886e2ec42f1efb2be339d" + integrity sha512-+3/GPwOgcoF0xLz/opTnahel1/y42PdcgZ4hs+BZGIUjtmEFSXGg+nFoaH3NSmuc7a6GSFwXDJ5L7VXpqzigNg== + dependencies: + "@types/node" "*" + playwright-core "1.29.2" + "@pmmmwh/react-refresh-webpack-plugin@^0.5.2": version "0.5.6" resolved "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.6.tgz" @@ -2184,9 +2192,9 @@ integrity sha512-HUAiN65VsRXyFCTicolwb5+I7FM6f72zjMWr+ajGk+YTvzBgXqa2A5U7d+rtsouAkunJ5U4Sb5lNJjo9w+nmXg== "@types/node@*": - version "17.0.33" - resolved "https://registry.npmjs.org/@types/node/-/node-17.0.33.tgz" - integrity sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ== + version "18.11.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" + integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== "@types/node@^14.14.31": version "14.18.18" @@ -10655,6 +10663,11 @@ pkg-dir@4.2.0, pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +playwright-core@1.29.2: + version "1.29.2" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.29.2.tgz#2e8347e7e8522409f22b244e600e703b64022406" + integrity sha512-94QXm4PMgFoHAhlCuoWyaBYKb92yOcGVHdQLoxQ7Wjlc7Flg4aC/jbFW7xMR52OfXMVkWicue4WXE7QEegbIRA== + plur@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/plur/-/plur-4.0.0.tgz"