From 3c21d583c5bcf3610e44091ae458414f6bf3430f Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Wed, 3 Jan 2024 19:03:11 +0400 Subject: [PATCH 1/2] Migrate 'embedding' e2e tests to Playwright --- .../specs/editor/various/embedding.spec.js | 310 ++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 test/e2e/specs/editor/various/embedding.spec.js diff --git a/test/e2e/specs/editor/various/embedding.spec.js b/test/e2e/specs/editor/various/embedding.spec.js new file mode 100644 index 0000000000000..9470efadf1f2f --- /dev/null +++ b/test/e2e/specs/editor/various/embedding.spec.js @@ -0,0 +1,310 @@ +/** + * WordPress dependencies + */ +const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + +/** @typedef {import('@playwright/test').Page} Page */ +/** @typedef {import('@wordpress/e2e-test-utils-playwright').Editor} Editor */ + +const EMBED_URLS = [ + '/oembed/1.0/proxy', + `rest_route=${ encodeURIComponent( '/oembed/1.0/proxy' ) }`, +]; + +const MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE = { + url: 'https://wordpress.org/gutenberg/handbook/block-api/attributes/', + html: '
', + type: 'rich', + provider_name: 'WordPress', + provider_url: 'https://wordpress.org', + version: '1.0', +}; + +const MOCK_EMBED_RICH_SUCCESS_RESPONSE = { + url: 'https://twitter.com/notnownikki', + html: '

Mock success response.

', + type: 'rich', + provider_name: 'Twitter', + provider_url: 'https://twitter.com', + version: '1.0', +}; + +const MOCK_EMBED_PHOTO_SUCCESS_RESPONSE = { + url: 'https://cloudup.com/cQFlxqtY4ob', + html: '

Mock success response.

', + type: 'photo', + provider_name: 'Cloudup', + provider_url: 'https://cloudup.com', + version: '1.0', +}; + +const MOCK_EMBED_VIDEO_SUCCESS_RESPONSE = { + url: 'https://www.youtube.com/watch?v=lXMskKTw3Bc', + html: '', + type: 'video', + provider_name: 'YouTube', + provider_url: 'https://youtube.com', + version: '1.0', +}; + +const MOCK_BAD_EMBED_PROVIDER_RESPONSE = { + url: 'https://twitter.com/thatbunty', + html: false, + provider_name: 'Embed Provider', + version: '1.0', +}; + +const MOCK_CANT_EMBED_RESPONSE = { + provider_name: 'Embed Handler', + html: 'https://twitter.com/wooyaygutenberg123454312', +}; + +const MOCK_BAD_WORDPRESS_RESPONSE = { + code: 'oembed_invalid_url', + message: 'Not Found', + data: { + status: 404, + }, + html: false, +}; + +test.use( { + embedUtils: async ( { page, editor }, use ) => { + await use( new EmbedUtils( { page, editor } ) ); + }, +} ); + +test.describe( 'Embedding content', () => { + test.beforeEach( async ( { admin } ) => { + await admin.createNewPost(); + } ); + + test( 'should render embeds in the correct state', async ( { + editor, + embedUtils, + } ) => { + await embedUtils.interceptRequests( { + 'https://twitter.com/notnownikki': MOCK_EMBED_RICH_SUCCESS_RESPONSE, + 'https://twitter.com/wooyaygutenberg123454312': + MOCK_CANT_EMBED_RESPONSE, + 'https://wordpress.org/gutenberg/handbook/': + MOCK_BAD_WORDPRESS_RESPONSE, + 'https://twitter.com/thatbunty': MOCK_BAD_EMBED_PROVIDER_RESPONSE, + 'https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/': + MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE, + 'https://www.youtube.com/watch?v=lXMskKTw3Bc': + MOCK_EMBED_VIDEO_SUCCESS_RESPONSE, + 'https://cloudup.com/cQFlxqtY4ob': + MOCK_EMBED_PHOTO_SUCCESS_RESPONSE, + } ); + + const currenEmbedBlock = editor.canvas + .getByRole( 'document', { name: 'Block' } ) + .last(); + + await embedUtils.insertEmbed( 'https://twitter.com/notnownikki' ); + await expect( + currenEmbedBlock.locator( 'iframe' ), + 'Valid embed. Should render valid element.' + ).toHaveAttribute( 'title', 'Embedded content from twitter' ); + + await embedUtils.insertEmbed( + 'https://twitter.com/wooyaygutenberg123454312' + ); + await expect( + currenEmbedBlock.getByRole( 'textbox', { name: 'Embed URL' } ), + 'Valid provider; invalid content. Should render failed, edit state.' + ).toHaveValue( 'https://twitter.com/wooyaygutenberg123454312' ); + + await embedUtils.insertEmbed( + 'https://wordpress.org/gutenberg/handbook/' + ); + await expect( + currenEmbedBlock.getByRole( 'textbox', { name: 'Embed URL' } ), + 'WordPress invalid content. Should render failed, edit state.' + ).toHaveValue( 'https://wordpress.org/gutenberg/handbook/' ); + + await embedUtils.insertEmbed( 'https://twitter.com/thatbunty' ); + await expect( + currenEmbedBlock.getByRole( 'textbox', { name: 'Embed URL' } ), + 'Provider whose oembed API has gone wrong. Should render failed, edit state.' + ).toHaveValue( 'https://twitter.com/thatbunty' ); + + await embedUtils.insertEmbed( + 'https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/' + ); + await expect( + currenEmbedBlock.locator( 'figure' ), + 'WordPress valid content. Should render valid figure element.' + ).toHaveClass( 'wp-block-embed' ); + + await embedUtils.insertEmbed( + 'https://www.youtube.com/watch?v=lXMskKTw3Bc' + ); + await expect( + currenEmbedBlock.locator( 'figure' ), + 'Video content. Should render valid figure element, and include the aspect ratio class.' + ).toHaveClass( /wp-embed-aspect-16-9/ ); + + await embedUtils.insertEmbed( 'https://cloudup.com/cQFlxqtY4ob' ); + await expect( + currenEmbedBlock.locator( 'iframe' ), + 'Photo content. Should render valid iframe element.' + ).toHaveAttribute( 'title', 'Embedded content from cloudup' ); + } ); + + test( 'should allow the user to convert unembeddable URLs to a paragraph with a link in it', async ( { + editor, + embedUtils, + } ) => { + await embedUtils.interceptRequests( { + 'https://twitter.com/wooyaygutenberg123454312': + MOCK_CANT_EMBED_RESPONSE, + } ); + await embedUtils.insertEmbed( + 'https://twitter.com/wooyaygutenberg123454312' + ); + + const embedBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Embed', + } ); + + await expect( embedBlock ).toContainText( + 'Sorry, this content could not be embedded.' + ); + + await embedBlock + .getByRole( 'button', { name: 'Convert to link' } ) + .click(); + + await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/paragraph', + attributes: { + content: + 'https://twitter.com/wooyaygutenberg123454312', + }, + }, + ] ); + } ); + + // @todo: See if there is regression for https://github.com/WordPress/gutenberg/pull/14705. + test.skip( 'should retry embeds that could not be embedded with trailing slashes, without the trailing slashes', async ( { + editor, + embedUtils, + } ) => { + await embedUtils.interceptRequests( { + 'https://twitter.com/notnownikki/': MOCK_CANT_EMBED_RESPONSE, + 'https://twitter.com/notnownikki': MOCK_EMBED_RICH_SUCCESS_RESPONSE, + } ); + await embedUtils.insertEmbed( 'https://twitter.com/notnownikki/' ); + + await expect( + editor.canvas.getByRole( 'document', { + name: 'Block: Twitter', + } ) + ).toBeVisible(); + } ); + + test( 'should allow the user to try embedding a failed URL again', async ( { + editor, + embedUtils, + } ) => { + await embedUtils.interceptRequests( { + 'https://twitter.com/wooyaygutenberg123454312': + MOCK_CANT_EMBED_RESPONSE, + } ); + await embedUtils.insertEmbed( + 'https://twitter.com/wooyaygutenberg123454312' + ); + + const embedBlock = editor.canvas.getByRole( 'document', { + name: 'Block: Embed', + } ); + + await expect( embedBlock ).toContainText( + 'Sorry, this content could not be embedded.' + ); + + await embedUtils.interceptRequests( { + 'https://twitter.com/wooyaygutenberg123454312': + MOCK_EMBED_RICH_SUCCESS_RESPONSE, + } ); + + await embedBlock.getByRole( 'button', { name: 'Try again' } ).click(); + await expect( + editor.canvas.getByRole( 'document', { + name: 'Block: Twitter', + } ) + ).toBeVisible(); + } ); + + test( 'should switch to the WordPress block correctly', async ( { + editor, + embedUtils, + requestUtils, + } ) => { + const post = await requestUtils.createPost( { + title: 'Local embed test', + content: 'Hello there!', + status: 'publish', + } ); + + await embedUtils.insertEmbed( post.link ); + await expect( + editor.canvas.getByRole( 'document', { name: 'Block: Embed' } ) + ).toBeVisible(); + } ); +} ); + +class EmbedUtils { + /** @type {Page} */ + #page; + /** @type {Editor} */ + #editor; + + constructor( { page, editor } ) { + this.#page = page; + this.#editor = editor; + } + + /** + * @param {URL} url + */ + isRESTRoute( url ) { + return EMBED_URLS.some( ( route ) => { + return url.href.includes( route ); + } ); + } + + async interceptRequests( responses ) { + await this.#page.route( + ( url ) => this.isRESTRoute( url ), + async ( route, request ) => { + const embedUrl = new URL( request.url() ).searchParams.get( + 'url' + ); + const response = responses[ embedUrl ]; + + if ( response ) { + await route.fulfill( { + json: response, + } ); + } else { + await route.continue(); + } + } + ); + } + + async insertEmbed( url ) { + await test.step( `Inserting embed ${ url }`, async () => { + await this.#editor.insertBlock( { name: 'core/embed' } ); + await this.#editor.canvas + .getByRole( 'textbox', { name: 'Embed URL' } ) + .last() + .fill( url ); + await this.#page.keyboard.press( 'Enter' ); + } ); + } +} From 455b0943cd9c75653a0353f37301aadbe1ef938b Mon Sep 17 00:00:00 2001 From: George Mamadashvili Date: Thu, 18 Jan 2024 17:55:09 +0400 Subject: [PATCH 2/2] Remove old test files --- .../__snapshots__/embedding.test.js.snap | 67 ---- .../specs/editor/various/embedding.test.js | 303 ------------------ .../specs/editor/various/embedding.spec.js | 4 +- 3 files changed, 2 insertions(+), 372 deletions(-) delete mode 100644 packages/e2e-tests/specs/editor/various/__snapshots__/embedding.test.js.snap delete mode 100644 packages/e2e-tests/specs/editor/various/embedding.test.js diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/embedding.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/embedding.test.js.snap deleted file mode 100644 index 328b727ac6533..0000000000000 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/embedding.test.js.snap +++ /dev/null @@ -1,67 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Embedding content should allow the user to convert unembeddable URLs to a paragraph with a link in it 1`] = ` -" -

https://twitter.com/wooyaygutenberg123454312

-" -`; - -exports[`Embedding content should allow the user to try embedding a failed URL again 1`] = ` -" -
-https://twitter.com/wooyaygutenberg123454312 -
-" -`; - -exports[`Embedding content should render embeds in the correct state 1`] = ` -" -
-https://twitter.com/notnownikki -
- - - -
-https://twitter.com/wooyaygutenberg123454312 -
- - - -
-https://wordpress.org/gutenberg/handbook/ -
- - - -
-https://twitter.com/thatbunty -
- - - -
-https://wordpress.org/gutenberg/handbook/block-api/attributes/ -
- - - -
-https://www.youtube.com/watch?v=lXMskKTw3Bc -
- - - -
-https://cloudup.com/cQFlxqtY4ob -
-" -`; - -exports[`Embedding content should retry embeds that could not be embedded with trailing slashes, without the trailing slashes 1`] = ` -" -
-https://twitter.com/notnownikki/ -
-" -`; diff --git a/packages/e2e-tests/specs/editor/various/embedding.test.js b/packages/e2e-tests/specs/editor/various/embedding.test.js deleted file mode 100644 index 4461fc6233053..0000000000000 --- a/packages/e2e-tests/specs/editor/various/embedding.test.js +++ /dev/null @@ -1,303 +0,0 @@ -/** - * WordPress dependencies - */ -import { - clickBlockAppender, - createEmbeddingMatcher, - createJSONResponse, - createNewPost, - createURLMatcher, - getEditedPostContent, - insertBlock, - publishPost, - setUpResponseMocking, - canvas, -} from '@wordpress/e2e-test-utils'; - -const MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE = { - url: 'https://wordpress.org/gutenberg/handbook/block-api/attributes/', - html: '
', - type: 'rich', - provider_name: 'WordPress', - provider_url: 'https://wordpress.org', - version: '1.0', -}; - -const MOCK_EMBED_RICH_SUCCESS_RESPONSE = { - url: 'https://twitter.com/notnownikki', - html: '

Mock success response.

', - type: 'rich', - provider_name: 'Twitter', - provider_url: 'https://twitter.com', - version: '1.0', -}; - -const MOCK_EMBED_PHOTO_SUCCESS_RESPONSE = { - url: 'https://cloudup.com/cQFlxqtY4ob', - html: '

Mock success response.

', - type: 'photo', - provider_name: 'Cloudup', - provider_url: 'https://cloudup.com', - version: '1.0', -}; - -const MOCK_EMBED_VIDEO_SUCCESS_RESPONSE = { - url: 'https://www.youtube.com/watch?v=lXMskKTw3Bc', - html: '', - type: 'video', - provider_name: 'YouTube', - provider_url: 'https://youtube.com', - version: '1.0', -}; - -const MOCK_EMBED_AUDIO_SUCCESS_RESPONSE = { - url: 'https://soundcloud.com/a-boogie-wit-da-hoodie/swervin', - html: '', - type: 'audio', - provider_name: 'SoundCloud', - provider_url: 'https://soundcloud.com', - version: '1.0', -}; - -const MOCK_EMBED_IMAGE_SUCCESS_RESPONSE = { - url: 'https://www.instagram.com/p/Bvl97o2AK6x/', - html: '', - type: 'video', - provider_name: 'Instagram', - provider_url: 'https://www.instagram.com', - version: '1.0', -}; - -const MOCK_BAD_EMBED_PROVIDER_RESPONSE = { - url: 'https://twitter.com/thatbunty', - html: false, - provider_name: 'Embed Provider', - version: '1.0', -}; - -const MOCK_CANT_EMBED_RESPONSE = { - provider_name: 'Embed Handler', - html: 'https://twitter.com/wooyaygutenberg123454312', -}; - -const MOCK_BAD_WORDPRESS_RESPONSE = { - code: 'oembed_invalid_url', - message: 'Not Found', - data: { - status: 404, - }, - html: false, -}; - -const MOCK_RESPONSES = [ - { - match: createEmbeddingMatcher( - 'https://wordpress.org/gutenberg/handbook' - ), - onRequestMatch: createJSONResponse( MOCK_BAD_WORDPRESS_RESPONSE ), - }, - { - match: createEmbeddingMatcher( - 'https://wordpress.org/gutenberg/handbook/' - ), - onRequestMatch: createJSONResponse( MOCK_BAD_WORDPRESS_RESPONSE ), - }, - { - match: createEmbeddingMatcher( - 'https://wordpress.org/gutenberg/handbook/block-api/attributes/' - ), - onRequestMatch: createJSONResponse( - MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE - ), - }, - { - match: createEmbeddingMatcher( - 'https://www.youtube.com/watch?v=lXMskKTw3Bc' - ), - onRequestMatch: createJSONResponse( MOCK_EMBED_VIDEO_SUCCESS_RESPONSE ), - }, - { - match: createEmbeddingMatcher( - 'https://soundcloud.com/a-boogie-wit-da-hoodie/swervin' - ), - onRequestMatch: createJSONResponse( MOCK_EMBED_AUDIO_SUCCESS_RESPONSE ), - }, - { - match: createEmbeddingMatcher( - 'https://www.instagram.com/p/Bvl97o2AK6x/' - ), - onRequestMatch: createJSONResponse( MOCK_EMBED_IMAGE_SUCCESS_RESPONSE ), - }, - { - match: createEmbeddingMatcher( 'https://cloudup.com/cQFlxqtY4ob' ), - onRequestMatch: createJSONResponse( MOCK_EMBED_PHOTO_SUCCESS_RESPONSE ), - }, - { - match: createEmbeddingMatcher( 'https://twitter.com/notnownikki' ), - onRequestMatch: createJSONResponse( MOCK_EMBED_RICH_SUCCESS_RESPONSE ), - }, - { - match: createEmbeddingMatcher( 'https://twitter.com/notnownikki/' ), - onRequestMatch: createJSONResponse( MOCK_CANT_EMBED_RESPONSE ), - }, - { - match: createEmbeddingMatcher( 'https://twitter.com/thatbunty' ), - onRequestMatch: createJSONResponse( MOCK_BAD_EMBED_PROVIDER_RESPONSE ), - }, - { - match: createEmbeddingMatcher( - 'https://twitter.com/wooyaygutenberg123454312' - ), - onRequestMatch: createJSONResponse( MOCK_CANT_EMBED_RESPONSE ), - }, - // Respond to the instagram URL with a non-image response, doesn't matter what it is, - // just make sure the image errors. - { - match: createURLMatcher( 'https://www.instagram.com/p/Bvl97o2AK6x/' ), - onRequestMatch: createJSONResponse( MOCK_CANT_EMBED_RESPONSE ), - }, -]; - -async function insertEmbed( URL ) { - await clickBlockAppender(); - await page.keyboard.type( '/embed' ); - await page.waitForXPath( - `//*[contains(@class, "components-autocomplete__result") and contains(@class, "is-selected") and contains(text(), 'Embed')]` - ); - await page.keyboard.press( 'Enter' ); - await page.keyboard.type( URL ); - await page.keyboard.press( 'Enter' ); -} - -describe( 'Embedding content', () => { - beforeEach( async () => { - await setUpResponseMocking( MOCK_RESPONSES ); - await createNewPost(); - } ); - - it( 'should render embeds in the correct state', async () => { - // Valid embed. Should render valid figure element. - await insertEmbed( 'https://twitter.com/notnownikki' ); - await canvas().waitForSelector( 'figure.wp-block-embed' ); - - // Valid provider; invalid content. Should render failed, edit state. - await insertEmbed( 'https://twitter.com/wooyaygutenberg123454312' ); - await canvas().waitForSelector( - 'input[value="https://twitter.com/wooyaygutenberg123454312"]' - ); - - // WordPress invalid content. Should render failed, edit state. - await insertEmbed( 'https://wordpress.org/gutenberg/handbook/' ); - await canvas().waitForSelector( - 'input[value="https://wordpress.org/gutenberg/handbook/"]' - ); - - // Provider whose oembed API has gone wrong. Should render failed, edit - // state. - await insertEmbed( 'https://twitter.com/thatbunty' ); - await canvas().waitForSelector( - 'input[value="https://twitter.com/thatbunty"]' - ); - - // WordPress content that can be embedded. Should render valid figure - // element. - await insertEmbed( - 'https://wordpress.org/gutenberg/handbook/block-api/attributes/' - ); - await canvas().waitForSelector( 'figure.wp-block-embed' ); - - // Video content. Should render valid figure element, and include the - // aspect ratio class. - await insertEmbed( 'https://www.youtube.com/watch?v=lXMskKTw3Bc' ); - await canvas().waitForSelector( - 'figure.wp-block-embed.is-type-video.wp-embed-aspect-16-9' - ); - - // Photo content. Should render valid figure element. - await insertEmbed( 'https://cloudup.com/cQFlxqtY4ob' ); - await canvas().waitForSelector( - 'iframe[title="Embedded content from cloudup"' - ); - - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should allow the user to convert unembeddable URLs to a paragraph with a link in it', async () => { - // URL that can't be embedded. - await insertEmbed( 'https://twitter.com/wooyaygutenberg123454312' ); - - // Wait for the request to fail and present an error. Since placeholder - // has styles applied which depend on resize observer, wait for the - // expected size class to settle before clicking, since otherwise a race - // condition could occur on the placeholder layout vs. click intent. - await canvas().waitForSelector( - '.components-placeholder.is-large .components-placeholder__error' - ); - - const button = await canvas().waitForXPath( - `//button[contains(text(), 'Convert to link')]` - ); - await button.click(); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should retry embeds that could not be embedded with trailing slashes, without the trailing slashes', async () => { - await insertEmbed( 'https://twitter.com/notnownikki/' ); - // The twitter block should appear correctly. - await canvas().waitForSelector( 'figure.wp-block-embed' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should allow the user to try embedding a failed URL again', async () => { - // URL that can't be embedded. - await insertEmbed( 'https://twitter.com/wooyaygutenberg123454312' ); - - // Wait for the request to fail and present an error. Since placeholder - // has styles applied which depend on resize observer, wait for the - // expected size class to settle before clicking, since otherwise a race - // condition could occur on the placeholder layout vs. click intent. - await canvas().waitForSelector( - '.components-placeholder.is-large .components-placeholder__error' - ); - - // Set up a different mock to make sure that try again actually does make the request again. - await setUpResponseMocking( [ - { - match: createEmbeddingMatcher( - 'https://twitter.com/wooyaygutenberg123454312' - ), - onRequestMatch: createJSONResponse( - MOCK_EMBED_RICH_SUCCESS_RESPONSE - ), - }, - ] ); - const button = await canvas().waitForXPath( - `//button[contains(text(), 'Try again')]` - ); - await button.click(); - await canvas().waitForSelector( 'figure.wp-block-embed' ); - expect( await getEditedPostContent() ).toMatchSnapshot(); - } ); - - it( 'should switch to the WordPress block correctly', async () => { - // This test is to make sure that WordPress embeds are detected correctly, - // because the HTML can vary, and the block is detected by looking for - // classes in the HTML, so we need to flag up if the HTML changes. - - // Publish a post to embed. - await insertBlock( 'Paragraph' ); - await page.keyboard.type( 'Hello there!' ); - await publishPost(); - const postUrl = await page.$eval( - '.editor-post-publish-panel [id^=inspector-text-control-]', - ( el ) => el.value - ); - - // Start a new post, embed the previous post. - await createNewPost(); - await insertEmbed( postUrl ); - - // Check the block has become a WordPress block. - await canvas().waitForSelector( 'figure.wp-block-embed' ); - } ); -} ); diff --git a/test/e2e/specs/editor/various/embedding.spec.js b/test/e2e/specs/editor/various/embedding.spec.js index 9470efadf1f2f..2fa963f66685d 100644 --- a/test/e2e/specs/editor/various/embedding.spec.js +++ b/test/e2e/specs/editor/various/embedding.spec.js @@ -12,7 +12,7 @@ const EMBED_URLS = [ ]; const MOCK_EMBED_WORDPRESS_SUCCESS_RESPONSE = { - url: 'https://wordpress.org/gutenberg/handbook/block-api/attributes/', + url: 'https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/', html: '
', type: 'rich', provider_name: 'WordPress', @@ -188,7 +188,7 @@ test.describe( 'Embedding content', () => { ] ); } ); - // @todo: See if there is regression for https://github.com/WordPress/gutenberg/pull/14705. + // Reason: A possible regression of https://github.com/WordPress/gutenberg/pull/14705. test.skip( 'should retry embeds that could not be embedded with trailing slashes, without the trailing slashes', async ( { editor, embedUtils,