Skip to content

Commit

Permalink
Playwright: use an interface for passing TestFile paths. (#55693)
Browse files Browse the repository at this point in the history
* Define and implement interface for TestFile.
- update Media (Upload) spec.
- update CoBlocks spec.
- update Blocks Media (Upload) spec.

* Correct comments referencing old way of obtaining file names.
Move Media Upload spec to TypeScript.

Update Media (Edit) spec

* Refactor how source test file paths are handled.
- new file `constants.js` to hold the source paths for the test files.
- call `createTestFile` by default and provide path.
- refactor the createTestFile method to use better variable names.

Revise behavior when hook fails.

Revise environment behavior again.
- set the `hookFailed` attribute to true if hook failure is experienced.
- add clause at `test_start` event to check for hook failure, and if detected, mark rest of the test cases as failed.

Rebase.
  • Loading branch information
worldomonation authored Sep 9, 2021
1 parent 1faca9b commit 091f787
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 94 deletions.
1 change: 1 addition & 0 deletions packages/calypso-e2e/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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 };

Expand Down
103 changes: 55 additions & 48 deletions packages/calypso-e2e/src/media-helper.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import { constants } from 'fs';
import fs from 'fs/promises';
import os from 'os';
import path from 'path';
import config from 'config';
import { getTimestamp } from './data-helper';

/**
* Interface for holding various parts of a filepath.
*/
export interface TestFile {
fullpath: string; // eg. /usr/home/wp-calypso/test/e2e/image-uploads/image.jpg
dirname: string; // eg. /usr/home/wp-calypso/test/e2e/image-uploads/
basename: string; // eg. image.jpg
filename: string; // eg. image
extension: string; // eg. .jpg
}

const artifacts: { [ key: string ]: string } = config.get( 'artifacts' );

/**
Expand Down Expand Up @@ -45,61 +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.
* @returns {Promise<string>} Full path to the generated test file.
* @param {string} [param0.postfix] Additional suffix to be used for the file.
* @returns {Promise<TestFile>} 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< string > {
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.` );
}

// Reassign the variable with the final name to be used, including the extension.
fileName = `${ fileName }.${ sourceFileName.split( '.' ).pop() }`;

const sourceFileDir = path.join( __dirname, '../../../../../test/e2e/image-uploads/' );
const sourceFilePath = path.join( sourceFileDir, sourceFileName );
// Obtain the file extension.
const extension = path.extname( sourcePath );
if ( ! extension ) {
throw new Error( `Extension not found on source file ${ sourcePath }` );
}

const tempDir = await fs.mkdtemp( path.join( os.tmpdir(), 'e2e-' ) );
const testFilePath = path.join( tempDir, fileName );
// 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 }`;
}

await fs.copyFile( sourceFilePath, testFilePath );
// Obtain the basename (filename with extension)
const basename = `${ filename }${ extension }`;

return testFilePath;
}

/**
* Returns the path to a generated temporary JPEG image file.
*
* @returns {Promise<string>} Full path on disk to the generated test file.
*/
export async function createTestImage(): Promise< string > {
return await createTestFile( { sourceFileName: 'image0.jpg' } );
}
const tempDir = await fs.mkdtemp( path.join( os.tmpdir(), 'e2e' ) );
const targetPath = path.join( tempDir, basename );

/**
* Returns the path to a generated temporary MP3 audio file.
*
* @returns {string} Full path on disk to the generated test file.
*/
export async function createTestAudio(): Promise< string > {
return await createTestFile( { sourceFileName: 'bees.mp3' } );
}
await fs.copyFile( sourcePath, targetPath, constants.COPYFILE_EXCL );

/**
* Returns the path to an unsupported file.
*
* @returns {string} Full path on disk to the generated test file.
*/
export async function createInvalidFile(): Promise< string > {
return await createTestFile( { sourceFileName: 'unsupported_extension.mkv' } );
// Return an object implementing the interface.
return {
fullpath: targetPath,
dirname: tempDir,
basename: basename,
filename: filename,
extension: extension,
};
}
12 changes: 12 additions & 0 deletions test/e2e/lib/jest/environment.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const JestEnvironmentNode = require( 'jest-environment-node' );

class JestEnvironmentE2E extends JestEnvironmentNode {
testFailed = false;
hookFailed = false;

async handleTestEvent( event ) {
switch ( event.name ) {
Expand All @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions test/e2e/specs/constants.js
Original file line number Diff line number Diff line change
@@ -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' )
);
15 changes: 8 additions & 7 deletions test/e2e/specs/specs-playwright/wp-blocks__coblocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* @group gutenberg
*/

import path from 'path';
import {
setupHooks,
DataHelper,
Expand All @@ -15,27 +14,29 @@ import {
HeroBlock,
ClicktoTweetBlock,
LogosBlock,
TestFile,
} from '@automattic/calypso-e2e';
import { Page } from 'playwright';
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;
let logoImage: string;
let logoImage: TestFile;

// Test data
const pricingTableBlockPrice = 888;
const heroBlockHeading = 'Hero heading';
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 () {
Expand Down Expand Up @@ -78,7 +79,7 @@ describe( DataHelper.createSuiteTitle( 'Blocks: CoBlocks' ), () => {
it( `Insert ${ LogosBlock.blockName } block and set image`, async function () {
const blockHandle = await gutenbergEditorPage.addBlock( LogosBlock.blockName );
const logosBlock = new LogosBlock( blockHandle );
await logosBlock.upload( logoImage );
await logosBlock.upload( logoImage.fullpath );
} );

it( 'Publish and visit post', async function () {
Expand All @@ -104,6 +105,6 @@ describe( DataHelper.createSuiteTitle( 'Blocks: CoBlocks' ), () => {
);

it( `Confirm Logos block is visible in published post`, async () => {
await LogosBlock.validatePublishedContent( page, path.parse( logoImage ).name );
await LogosBlock.validatePublishedContent( page, [ logoImage.filename ] );
} );
} );
48 changes: 25 additions & 23 deletions test/e2e/specs/specs-playwright/wp-blocks__media-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* @group gutenberg
*/

import path from 'path';
import {
setupHooks,
DataHelper,
Expand All @@ -14,26 +13,27 @@ import {
ImageBlock,
AudioBlock,
FileBlock,
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: string; image_reserved_name: string; audio: string };
let testFiles: { image: TestFile; image_reserved_name: TestFile; audio: TestFile };

setupHooks( ( args ) => {
page = args.page;
} );

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 ),
};
} );

Expand All @@ -55,47 +55,49 @@ describe( DataHelper.createSuiteTitle( 'Blocks: Media (Upload)' ), () => {
it( `${ ImageBlock.blockName } block: upload image file`, async function () {
const blockHandle = await gutenbergEditorPage.addBlock( ImageBlock.blockName );
const imageBlock = new ImageBlock( blockHandle );
await imageBlock.upload( testFiles.image );
await imageBlock.upload( testFiles.image.fullpath );
} );

it( `${ ImageBlock.blockName } block: upload image file with reserved URL characters`, async function () {
const blockHandle = await gutenbergEditorPage.addBlock( ImageBlock.blockName );
const imageBlock = new ImageBlock( blockHandle );
await imageBlock.upload( testFiles.image_reserved_name );
await imageBlock.upload( testFiles.image_reserved_name.fullpath );
} );

it( `${ AudioBlock.blockName } block: upload audio file`, async function () {
const blockHandle = await gutenbergEditorPage.addBlock( AudioBlock.blockName );
const audioBlock = new AudioBlock( blockHandle );
await audioBlock.upload( testFiles.audio );
await audioBlock.upload( testFiles.audio.fullpath );
} );

it( `${ FileBlock.blockName } block: upload audio file`, async function () {
const blockHandle = await gutenbergEditorPage.addBlock( FileBlock.blockName );
const fileBlock = new FileBlock( blockHandle );
await fileBlock.upload( testFiles.audio );
await fileBlock.upload( testFiles.audio.fullpath );
} );

it( 'Publish and visit post', async function () {
await gutenbergEditorPage.publish( { visit: true } );
// Must save as draft first to bypass issue with post-publish panel being auto-dismissed
// after publishing. May be related to the following issue?
// See https://github.com/Automattic/wp-calypso/issues/54421.
await gutenbergEditorPage.publish( { visit: true, saveDraft: true } );
} );

it( `Confirm Image block is visible in published post`, async () => {
await ImageBlock.validatePublishedContent( page, path.parse( testFiles.image ).name );
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,
path.parse( testFiles.image_reserved_name ).name.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, path.parse( testFiles.audio ).name );
it( `Confirm File block is visible in published post`, async function () {
await FileBlock.validatePublishedContent( page, [ testFiles.audio.filename ] );
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,29 @@ 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`
siteType | user
${ '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 );
Expand All @@ -51,7 +54,7 @@ describe( DataHelper.createSuiteTitle( 'Media: Edit Media' ), function () {
it( 'Upload image', async function () {
// Ideally, we'd not want to upload an image (that's a separate test)
// but occasionally, the photo gallery is cleaned out leaving no images.
const uploadedImageHandle = await mediaPage.upload( testImage );
const uploadedImageHandle = await mediaPage.upload( testImage.fullpath );
const isVisible = await uploadedImageHandle.isVisible();
expect( isVisible ).toBe( true );
} );
Expand Down
Loading

0 comments on commit 091f787

Please sign in to comment.