diff --git a/packages/calypso-e2e/src/index.ts b/packages/calypso-e2e/src/index.ts index b3a1517aec140..b3750a222277f 100644 --- a/packages/calypso-e2e/src/index.ts +++ b/packages/calypso-e2e/src/index.ts @@ -3,12 +3,10 @@ import * as BrowserManager from './browser-manager'; import * as DataHelper from './data-helper'; import * as ElementHelper from './element-helper'; import * as MediaHelper from './media-helper'; +export type { TestFile } from './media-helper'; export { BrowserHelper, BrowserManager, MediaHelper, DataHelper, ElementHelper }; export * from './lib'; export * from './hooks'; export * from './email-client'; -export * from './types'; - -export type { TestFile } from './media-helper'; diff --git a/packages/calypso-e2e/src/media-helper.ts b/packages/calypso-e2e/src/media-helper.ts index bcbc712a4ad75..43d6a2d2f0af3 100644 --- a/packages/calypso-e2e/src/media-helper.ts +++ b/packages/calypso-e2e/src/media-helper.ts @@ -1,3 +1,4 @@ +import { constants } from 'fs'; import fs from 'fs/promises'; import os from 'os'; import path from 'path'; @@ -56,72 +57,56 @@ export function getVideoDir(): string { /** * Creates a temporary test file by cloning a source file under a new name. * + * @param {string} sourcePath Full path on disk of the source file. * @param {{[key: string]: string}} param0 Parameter object. - * @param {string} param0.sourceFileName Basename of the source file to be cloned. - * @param {string} [param0.testFileName] Basename of the test file to be generated. + * @param {string} [param0.postfix] Additional suffix to be used for the file. * @returns {Promise} Object implementing the TestFile interface. + * @throws {Error} If source file was not found, or source file did not contain an extension. */ -export async function createTestFile( { - sourceFileName, - testFileName, -}: { - sourceFileName: string; - testFileName?: string; -} ): Promise< TestFile > { - let filename = getTimestamp(); - // If the output `testFileName` is defined, use that as part of the final filename. - if ( testFileName ) { - filename += `-${ testFileName }`; +export async function createTestFile( + sourcePath: string, + { + postfix, + }: { + postfix?: string; + } = {} +): Promise< TestFile > { + // Check whether the source file maps to a file. + // Note, if sourcePath is not found use console.error instead of throw: + // https://github.com/facebook/jest/issues/8688 + try { + await fs.access( sourcePath ); + } catch { + throw new Error( `Source file ${ sourcePath } not found on disk.` ); } - const extension = sourceFileName.split( '.' ).pop(); + // Obtain the file extension. + const extension = path.extname( sourcePath ); if ( ! extension ) { - throw new Error( `Extension not found on source file ${ sourceFileName }` ); + throw new Error( `Extension not found on source file ${ sourcePath }` ); + } + + // Generate a filename using current timestamp and a pseudo-randomly generated integer. + let filename = getTimestamp(); + // If `postfix` is defined, use that as part of the final filename. + if ( postfix ) { + filename += `-${ postfix }`; } - const basename = `${ filename }.${ sourceFileName.split( '.' ).pop() }`; - // Create test files in the same directory as the source file. - const dirname = path.join( __dirname, '' ); - // Full path on disk of the source file, to be copied and renamed. - const sourceFilePath = path.join( dirname, sourceFileName ); - const tempDir = await fs.mkdtemp( path.join( os.tmpdir(), 'e2e-' ) ); - const testFilePath = path.join( tempDir, basename ); + // Obtain the basename (filename with extension) + const basename = `${ filename }${ extension }`; + + const tempDir = await fs.mkdtemp( path.join( os.tmpdir(), 'e2e' ) ); + const targetPath = path.join( tempDir, basename ); - await fs.copyFile( sourceFilePath, testFilePath ); + await fs.copyFile( sourcePath, targetPath, constants.COPYFILE_EXCL ); // Return an object implementing the interface. return { - fullpath: testFilePath, - dirname: dirname, + fullpath: targetPath, + dirname: tempDir, basename: basename, filename: filename, extension: extension, }; } - -/** - * Returns the path to a generated temporary JPEG image file. - * - * @returns {Promise} Object implementing the TestFile interface. - */ -export async function createTestImage(): Promise< TestFile > { - return await createTestFile( { sourceFileName: 'image0.jpg' } ); -} - -/** - * Returns the path to a generated temporary MP3 audio file. - * - * @returns {Promise} Object implementing the TestFile interface. - */ -export async function createTestAudio(): Promise< TestFile > { - return await createTestFile( { sourceFileName: 'bees.mp3' } ); -} - -/** - * Returns the path to an unsupported file. - * - * @returns {Promise} Object implementing the TestFile interface. - */ -export async function createUnsupportedFile(): Promise< TestFile > { - return await createTestFile( { sourceFileName: 'unsupported_extension.mkv' } ); -} diff --git a/packages/calypso-e2e/src/types.d.ts b/packages/calypso-e2e/src/types.d.ts index 1c3295385e251..11ce20f1c6526 100644 --- a/packages/calypso-e2e/src/types.d.ts +++ b/packages/calypso-e2e/src/types.d.ts @@ -6,5 +6,3 @@ export type viewportSize = { width: number; height: number; }; - -export type { TestFile } from './media-helper'; diff --git a/packages/calypso-e2e/test-files/bees.mp3 b/packages/calypso-e2e/test-files/bees.mp3 deleted file mode 100644 index fa82c60bf8541..0000000000000 Binary files a/packages/calypso-e2e/test-files/bees.mp3 and /dev/null differ diff --git a/packages/calypso-e2e/test-files/image0.jpg b/packages/calypso-e2e/test-files/image0.jpg deleted file mode 100644 index a92ae7592944f..0000000000000 Binary files a/packages/calypso-e2e/test-files/image0.jpg and /dev/null differ diff --git a/packages/calypso-e2e/test-files/image1.jpg b/packages/calypso-e2e/test-files/image1.jpg deleted file mode 100644 index 9dcc016b5db24..0000000000000 Binary files a/packages/calypso-e2e/test-files/image1.jpg and /dev/null differ diff --git a/packages/calypso-e2e/test-files/image2.jpg b/packages/calypso-e2e/test-files/image2.jpg deleted file mode 100644 index 92ef89cecbf17..0000000000000 Binary files a/packages/calypso-e2e/test-files/image2.jpg and /dev/null differ diff --git a/packages/calypso-e2e/test-files/unsupported_extension.mkv b/packages/calypso-e2e/test-files/unsupported_extension.mkv deleted file mode 100644 index a92ae7592944f..0000000000000 Binary files a/packages/calypso-e2e/test-files/unsupported_extension.mkv and /dev/null differ diff --git a/test/e2e/lib/jest/environment.js b/test/e2e/lib/jest/environment.js index 4a5961fccb902..37cdc6f2c9f25 100644 --- a/test/e2e/lib/jest/environment.js +++ b/test/e2e/lib/jest/environment.js @@ -2,6 +2,7 @@ const JestEnvironmentNode = require( 'jest-environment-node' ); class JestEnvironmentE2E extends JestEnvironmentNode { testFailed = false; + hookFailed = false; async handleTestEvent( event ) { switch ( event.name ) { @@ -16,9 +17,20 @@ class JestEnvironmentE2E extends JestEnvironmentNode { if ( this.testFailed ) { event.test.mode = 'skip'; } + // With this flag enabled, all test cases in the describe + // block will be marked as failed. + // This way all other hooks (screenshot/recording) are run, + // and Jest correctly exits after those hooks are run. + if ( this.hookFailed ) { + event.test.mode = 'fail'; + } break; case 'hook_failure': + this.global.__CURRENT_TEST_FAILED__ = true; + this.hookFailed = true; + break; + case 'test_fn_failure': this.global.__CURRENT_TEST_FAILED__ = true; this.testFailed = true; diff --git a/test/e2e/specs/constants.js b/test/e2e/specs/constants.js new file mode 100644 index 0000000000000..baca2996de5f9 --- /dev/null +++ b/test/e2e/specs/constants.js @@ -0,0 +1,11 @@ +import path from 'path'; + +export const TEST_IMAGE_PATH = path.normalize( + path.join( __dirname, '..', 'image-uploads', 'image0.jpg' ) +); +export const TEST_AUDIO_PATH = path.normalize( + path.join( __dirname, '..', 'image-uploads', 'bees.mp3' ) +); +export const UNSUPPORTED_FILE_PATH = path.normalize( + path.join( __dirname, '..', 'image-uploads', 'unsupported_extension.mkv' ) +); diff --git a/test/e2e/specs/specs-playwright/wp-blocks__coblocks.ts b/test/e2e/specs/specs-playwright/wp-blocks__coblocks.ts index a20a4a3c583b7..a2634a637f950 100644 --- a/test/e2e/specs/specs-playwright/wp-blocks__coblocks.ts +++ b/test/e2e/specs/specs-playwright/wp-blocks__coblocks.ts @@ -2,7 +2,6 @@ * @group gutenberg */ -import path from 'path'; import { setupHooks, DataHelper, @@ -15,11 +14,12 @@ import { HeroBlock, ClicktoTweetBlock, LogosBlock, + TestFile, } from '@automattic/calypso-e2e'; import { Page } from 'playwright'; -import type { TestFile } from '@automattic/calypso-e2e'; +import { TEST_IMAGE_PATH } from '../constants'; -describe( DataHelper.createSuiteTitle( 'Blocks: CoBlocks' ), () => { +describe( DataHelper.createSuiteTitle( 'Blocks: CoBlocks' ), function () { let gutenbergEditorPage: GutenbergEditorPage; let pricingTableBlock: PricingTableBlock; let page: Page; @@ -31,12 +31,12 @@ describe( DataHelper.createSuiteTitle( 'Blocks: CoBlocks' ), () => { const clicktoTweetBlockTweet = 'The foolish man seeks happiness in the distance. The wise grows it under his feet. — James Oppenheim'; - setupHooks( ( args ) => { + setupHooks( ( args: { page: Page } ) => { page = args.page; } ); beforeAll( async () => { - logoImage = await MediaHelper.createTestImage(); + logoImage = await MediaHelper.createTestFile( TEST_IMAGE_PATH ); } ); it( 'Log in', async function () { @@ -105,6 +105,6 @@ describe( DataHelper.createSuiteTitle( 'Blocks: CoBlocks' ), () => { ); it( `Confirm Logos block is visible in published post`, async () => { - await LogosBlock.validatePublishedContent( page, logoImage.basename ); + await LogosBlock.validatePublishedContent( page, [ logoImage.filename ] ); } ); } ); diff --git a/test/e2e/specs/specs-playwright/wp-blocks__media-spec.ts b/test/e2e/specs/specs-playwright/wp-blocks__media-spec.ts index aab274b8d644c..f87abf0312f03 100644 --- a/test/e2e/specs/specs-playwright/wp-blocks__media-spec.ts +++ b/test/e2e/specs/specs-playwright/wp-blocks__media-spec.ts @@ -3,7 +3,6 @@ * @group gutenberg */ -import path from 'path'; import { setupHooks, DataHelper, @@ -17,8 +16,9 @@ import { TestFile, } from '@automattic/calypso-e2e'; import { Page } from 'playwright'; +import { TEST_IMAGE_PATH, TEST_AUDIO_PATH } from '../constants'; -describe( DataHelper.createSuiteTitle( 'Blocks: Media (Upload)' ), () => { +describe( DataHelper.createSuiteTitle( 'Blocks: Media (Upload)' ), function () { let gutenbergEditorPage: GutenbergEditorPage; let page: Page; let testFiles: { image: TestFile; image_reserved_name: TestFile; audio: TestFile }; @@ -29,12 +29,11 @@ describe( DataHelper.createSuiteTitle( 'Blocks: Media (Upload)' ), () => { beforeAll( async () => { testFiles = { - image: await MediaHelper.createTestImage(), - image_reserved_name: await MediaHelper.createTestFile( { - sourceFileName: 'image0.jpg', - testFileName: 'filewith#?#?reservedurlchars', + image: await MediaHelper.createTestFile( TEST_IMAGE_PATH ), + image_reserved_name: await MediaHelper.createTestFile( TEST_IMAGE_PATH, { + postfix: 'filewith#?#?reservedurlchars', } ), - audio: await MediaHelper.createTestAudio(), + audio: await MediaHelper.createTestFile( TEST_AUDIO_PATH ), }; } ); @@ -84,22 +83,21 @@ describe( DataHelper.createSuiteTitle( 'Blocks: Media (Upload)' ), () => { await gutenbergEditorPage.publish( { visit: true, saveDraft: true } ); } ); - it( `Confirm Image block is visible in published post`, async () => { - await ImageBlock.validatePublishedContent( page, testFiles.image.filename ); + it( `Confirm Image block is visible in published post`, async function () { + await ImageBlock.validatePublishedContent( page, [ testFiles.image.filename ] ); } ); - it( `Confirm Image block is visible in published post (reserved name)`, async () => { - await ImageBlock.validatePublishedContent( - page, - testFiles.image_reserved_name.filename.replace( /[^a-zA-Z ]/g, '' ) - ); + it( `Confirm Image block is visible in published post (reserved name)`, async function () { + await ImageBlock.validatePublishedContent( page, [ + testFiles.image_reserved_name.filename.replace( /[^a-zA-Z ]/g, '' ), + ] ); } ); - it( `Confirm Audio block is visible in published post`, async () => { + it( `Confirm Audio block is visible in published post`, async function () { await AudioBlock.validatePublishedContent( page ); } ); - it( `Confirm File block is visible in published post`, async () => { - await FileBlock.validatePublishedContent( page, testFiles.audio.filename ); + it( `Confirm File block is visible in published post`, async function () { + await FileBlock.validatePublishedContent( page, [ testFiles.audio.filename ] ); } ); } ); diff --git a/test/e2e/specs/specs-playwright/wp-media__edit-spec.js b/test/e2e/specs/specs-playwright/wp-media__edit-spec.ts similarity index 87% rename from test/e2e/specs/specs-playwright/wp-media__edit-spec.js rename to test/e2e/specs/specs-playwright/wp-media__edit-spec.ts index 12e1ddc7d478d..9247b75630db2 100644 --- a/test/e2e/specs/specs-playwright/wp-media__edit-spec.js +++ b/test/e2e/specs/specs-playwright/wp-media__edit-spec.ts @@ -9,18 +9,21 @@ import { MediaPage, SidebarComponent, setupHooks, + TestFile, } from '@automattic/calypso-e2e'; +import { Page } from 'playwright'; +import { TEST_IMAGE_PATH } from '../constants'; describe( DataHelper.createSuiteTitle( 'Media: Edit Media' ), function () { - let testImage; - let page; + let testImage: TestFile; + let page: Page; setupHooks( ( args ) => { page = args.page; } ); beforeAll( async () => { - testImage = await MediaHelper.createTestImage(); + testImage = await MediaHelper.createTestFile( TEST_IMAGE_PATH ); } ); describe.each` @@ -28,7 +31,7 @@ describe( DataHelper.createSuiteTitle( 'Media: Edit Media' ), function () { ${ 'Simple' } | ${ 'defaultUser' } ${ 'Atomic' } | ${ 'wooCommerceUser' } `( 'Edit Image ($siteType)', function ( { user } ) { - let mediaPage; + let mediaPage: MediaPage; it( 'Log In', async function () { const loginFlow = new LoginFlow( page, user ); diff --git a/test/e2e/specs/specs-playwright/wp-media__upload-spec.ts b/test/e2e/specs/specs-playwright/wp-media__upload-spec.ts index 04934c1374e9c..d378a0a535c25 100644 --- a/test/e2e/specs/specs-playwright/wp-media__upload-spec.ts +++ b/test/e2e/specs/specs-playwright/wp-media__upload-spec.ts @@ -13,6 +13,7 @@ import { TestFile, } from '@automattic/calypso-e2e'; import { Page } from 'playwright'; +import { TEST_IMAGE_PATH, TEST_AUDIO_PATH, UNSUPPORTED_FILE_PATH } from '../constants'; describe( DataHelper.createSuiteTitle( 'Media: Upload' ), () => { let testFiles: { image: TestFile; audio: TestFile; unsupported: TestFile }; @@ -24,9 +25,9 @@ describe( DataHelper.createSuiteTitle( 'Media: Upload' ), () => { beforeAll( async () => { testFiles = { - image: await MediaHelper.createTestImage(), - audio: await MediaHelper.createTestAudio(), - unsupported: await MediaHelper.createUnsupportedFile(), + image: await MediaHelper.createTestFile( TEST_IMAGE_PATH ), + audio: await MediaHelper.createTestFile( TEST_AUDIO_PATH ), + unsupported: await MediaHelper.createTestFile( UNSUPPORTED_FILE_PATH ), }; } ); @@ -53,18 +54,18 @@ describe( DataHelper.createSuiteTitle( 'Media: Upload' ), () => { } ); it( 'Upload image and confirm addition to gallery', async () => { - const uploadedItem = await mediaPage.upload( testFiles.image ); + const uploadedItem = await mediaPage.upload( testFiles.image.fullpath ); assert.strictEqual( await uploadedItem.isVisible(), true ); } ); it( 'Upload audio and confirm addition to gallery', async () => { - const uploadedItem = await mediaPage.upload( testFiles.audio ); + const uploadedItem = await mediaPage.upload( testFiles.audio.fullpath ); assert.strictEqual( await uploadedItem.isVisible(), true ); } ); it( 'Upload an unsupported file type and see the rejection notice', async function () { try { - await mediaPage.upload( testFiles.unsupported ); + await mediaPage.upload( testFiles.unsupported.fullpath ); } catch ( error ) { assert.match( error.message, /could not be uploaded/i ); } diff --git a/test/e2e/tsconfig.json b/test/e2e/tsconfig.json index e8e24bbf696b6..0ff0821b4906e 100644 --- a/test/e2e/tsconfig.json +++ b/test/e2e/tsconfig.json @@ -1,9 +1,9 @@ { "extends": "@automattic/calypso-build/typescript/ts-package.json", "compilerOptions": { + "allowJs": true, "noEmit": true, // just type checking, no output. The output is handled by babel. "types": [ "jest", "node" ] // no mocha - we are only using TypeScript for the new Playwright scripts }, - // TypeScript is scoped only for the new Playwright scripts - "include": [ "specs/specs-playwright", "lib/gutenberg/tracking" ] + "include": [ "specs/specs-playwright", "specs/constants.js", "lib/gutenberg/tracking" ] }