Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci(e2e): Migrate tests to Jest #53702

Closed
wants to merge 31 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7a0237b
Debug test capture
scinos Jun 16, 2021
174d21b
Fix path
scinos Jun 16, 2021
4692b5b
Use sync
scinos Jun 16, 2021
4569673
Move eslint config to test/e2e
scinos Jun 15, 2021
ea469c7
Migrate Calypso e2e tests to Jest
scinos Jun 15, 2021
d5e526e
Run jest test instead of magellan
scinos Jun 15, 2021
59712ea
Add script to update chromedriver
scinos Jun 16, 2021
86682f9
Do not use this.
scinos Jun 16, 2021
215bde8
Run tests in verbose mode
scinos Jun 16, 2021
802081a
Use jest-circus and custom environment to implement fail fast
scinos Jun 16, 2021
bfd96f6
Restore missing tests
scinos Jun 16, 2021
aaf2109
Restore skipped test
scinos Jun 16, 2021
f31b7cf
Run Mobile tests using jest as well
scinos Jun 16, 2021
056bf1d
Drop noisy log
scinos Jun 17, 2021
df2e814
Add TEAMCITY_VERSION env var
scinos Jun 23, 2021
3178b92
Use hooks
scinos Jun 23, 2021
b81a2f7
Fix typo in push
scinos Jun 25, 2021
c471950
Add missing imports
scinos Jun 25, 2021
88395b8
Save logs in test directory
scinos Jun 25, 2021
8aa5e3d
Fix prettier errors
scinos Jun 25, 2021
92a1c25
Fix more eslint errors
scinos Jun 25, 2021
a5b4965
Fix typo in parameters
scinos Jun 25, 2021
1f0d5ba
Move config files
scinos Jul 5, 2021
81d103d
USe hyphen for file names
scinos Jul 5, 2021
2695b51
Save test status and name in the global scope
scinos Jul 5, 2021
db37a23
Harden closing ffVideo
scinos Jul 5, 2021
a3ba2ff
Add more debug options
scinos Jul 5, 2021
3d61c18
Get test name from expect state
scinos Jul 5, 2021
511a5a9
Create results dir
scinos Jul 5, 2021
6f4e443
Use APIs correctly
scinos Jul 5, 2021
95641b9
Make takeScreenshot more robust
scinos Jul 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,28 +65,6 @@ module.exports = {
'valid-jsdoc': 'off',
},
},
{
plugins: [ 'mocha' ],
files: [ 'test/e2e/**/*', 'packages/magellan-mocha-plugin/test_support/**/*' ],
rules: {
'import/no-nodejs-modules': 'off',
'mocha/no-exclusive-tests': 'error',
'mocha/handle-done-callback': [ 'error', { ignoreSkipped: true } ],
'mocha/no-global-tests': 'error',
'mocha/no-async-describe': 'error',
'mocha/no-top-level-hooks': 'error',
'mocha/max-top-level-suites': [ 'error', { limit: 1 } ],
'no-console': 'off',
// Disable all rules from "plugin:jest/recommended", as e2e tests use mocha
...Object.keys( require( 'eslint-plugin-jest' ).configs.recommended.rules ).reduce(
( disabledRules, key ) => ( { ...disabledRules, [ key ]: 'off' } ),
{}
),
},
globals: {
step: false,
},
},
merge(
// ESLint doesn't allow the `extends` field inside `overrides`, so we need to compose
// the TypeScript config manually using internal bits from various plugins
Expand Down
8 changes: 6 additions & 2 deletions .teamcity/_self/projects/WebApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ object RunCalypsoE2eDesktopTests : BuildType({
export NODE_CONFIG_ENV=test
export TEST_VIDEO=true
export HIGHLIGHT_ELEMENT=true
export TEAMCITY_VERSION=2020

# Instructs Magellan to not hide the output from individual `mocha` processes. This is required for
# mocha-teamcity-reporter to work.
Expand Down Expand Up @@ -104,7 +105,8 @@ object RunCalypsoE2eDesktopTests : BuildType({
export BROWSERLOCALE="en"
export NODE_CONFIG="{\"calypsoBaseURL\":\"${'$'}{URL%/}\"}"

yarn magellan --config=magellan-calypso.json --max_workers=%E2E_WORKERS% --local_browser=chrome --mocha_args="--reporter mocha-multi-reporters --reporter-options configFile=mocha-reporter.json"
# --reporters can't be the last arg, see https://github.com/facebook/jest/issues/10257
yarn jest --reporters=jest-teamcity --reporters=default --testNamePattern @parallel --maxWorkers=%E2E_WORKERS% specs/specs-calypso
""".trimIndent()
dockerImage = "%docker_image_e2e%"
dockerRunParameters = "-u %env.UID% --security-opt seccomp=.teamcity/docker-seccomp.json --shm-size=8gb"
Expand Down Expand Up @@ -228,6 +230,7 @@ object RunCalypsoE2eMobileTests : BuildType({
export NODE_CONFIG_ENV=test
export TEST_VIDEO=true
export HIGHLIGHT_ELEMENT=true
export TEAMCITY_VERSION=2020

# Instructs Magellan to not hide the output from individual `mocha` processes. This is required for
# mocha-teamcity-reporter to work.
Expand Down Expand Up @@ -266,7 +269,8 @@ object RunCalypsoE2eMobileTests : BuildType({
export BROWSERLOCALE="en"
export NODE_CONFIG="{\"calypsoBaseURL\":\"${'$'}{URL%/}\"}"

yarn magellan --config=magellan-calypso.json --max_workers=%E2E_WORKERS% --local_browser=chrome --mocha_args="--reporter mocha-multi-reporters --reporter-options configFile=mocha-reporter.json"
# --reporters can't be the last arg, see https://github.com/facebook/jest/issues/10257
yarn jest --reporters=jest-teamcity --reporters=default --testNamePattern @parallel --maxWorkers=%E2E_WORKERS% specs/specs-calypso
""".trimIndent()
dockerImage = "%docker_image_e2e%"
dockerRunParameters = "-u %env.UID% --security-opt seccomp=.teamcity/docker-seccomp.json --shm-size=8gb"
Expand Down
40 changes: 40 additions & 0 deletions test/e2e/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
module.exports = {
env: {
node: true,
mocha: false,
},
overrides: [
{
plugins: [ 'mocha' ],
files: [
'test/e2e/specs/specs-jetpack/*',
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enable mocha and disable jest plugin & rules for these paths

'test/e2e/specs/specs-playwright/*',
'test/e2e/specs/specs-wpcom/*',
],
rules: {
'mocha/no-exclusive-tests': 'error',
'mocha/handle-done-callback': [ 'error', { ignoreSkipped: true } ],
'mocha/no-global-tests': 'error',
'mocha/no-async-describe': 'error',
'mocha/no-top-level-hooks': 'error',
'mocha/max-top-level-suites': [ 'error', { limit: 1 } ],
// Disable all rules from "plugin:jest/recommended", as e2e tests use mocha
...Object.keys( require( 'eslint-plugin-jest' ).configs.recommended.rules ).reduce(
( disabledRules, key ) => ( { ...disabledRules, [ key ]: 'off' } ),
{}
),
},
globals: {
step: false,
},
},
],
rules: {
'import/no-nodejs-modules': 'off',
'no-console': 'off',

// We have many tests that don't make an explicit `expect`, but instead puts the browser
// in certain state that will be used by the next test, or asserted by WebDriver
'jest/expect-expect': 'off',
},
};
1 change: 1 addition & 0 deletions test/e2e/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"explicitWaitMS": 20000,
"emailWaitMS": 60000,
"mochaTimeoutMS": 160000,
"jestTimeoutMS": 160000,
"mochaDevDocsTimeoutMS": 1000000,
"startBrowserTimeoutMS": 60000,
"startAppTimeoutMS": 240000,
Expand Down
1 change: 1 addition & 0 deletions test/e2e/config/live-branches.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"calypsoBaseURL": "https://calypso.live",
"explicitWaitMS": 40000,
"mochaTimeoutMS": 320000,
"jestTimeoutMS": 320000,
"browser": "chrome",
"saveAllScreenshots": true
}
12 changes: 12 additions & 0 deletions test/e2e/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
cacheDirectory: '<rootDir>/../../.cache/jest',
testMatch: [ '<rootDir>/specs/**/*.js' ],
globalSetup: '<rootDir>/lib/jest/jest-global-setup.js',
setupFilesAfterEnv: [ '<rootDir>/lib/jest/jest-setup.js', '<rootDir>/lib/hooks/jest.js' ],
verbose: true,
transform: {
'\\.[jt]sx?$': [ 'babel-jest', { configFile: '../../babel.config.js' } ],
},
testRunner: 'jest-circus/runner',
testEnvironment: '<rootDir>/lib/jest/jest-environment-e2e.js',
};
7 changes: 4 additions & 3 deletions test/e2e/lib/driver-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import SauceLabs from 'saucelabs';
import { times } from 'lodash';
import { getChromeVersion } from '@testim/chrome-version';
import * as remote from 'selenium-webdriver/remote';
import path from 'path';

/**
* Internal dependencies
*/
import { generatePath } from './test-utils';
import * as dataHelper from './data-helper';

const webDriverImplicitTimeOutMS = 2000;
Expand Down Expand Up @@ -118,6 +118,7 @@ export async function startBrowser( {
useCustomUA = true,
resizeBrowserWindow = true,
disableThirdPartyCookies = false,
tempDir = null,
} = {} ) {
const screenSize = currentScreenSize();
const locale = currentLocale();
Expand Down Expand Up @@ -224,10 +225,10 @@ export async function startBrowser( {

// eslint-disable-next-line no-case-declarations
const service = new chrome.ServiceBuilder( chromedriver.path )
.loggingTo( generatePath( 'chromedriver.log' ) )
.loggingTo( path.join( tempDir, 'chromedriver.log' ) )
.build();
chrome.setDefaultService( service );
options.setChromeLogFile( generatePath( './chrome.log' ) );
options.setChromeLogFile( path.join( tempDir, 'chrome.log' ) );
options.addArguments( '--enable-logging' );

builder = new webdriver.Builder();
Expand Down
2 changes: 0 additions & 2 deletions test/e2e/lib/flows/login-flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ export default class LoginFlow {
// return;
// }

console.log( 'Logging in as ' + this.account.username );

let loginURL = this.account.loginURL;
let loginPage;

Expand Down
4 changes: 2 additions & 2 deletions test/e2e/lib/hooks/browser/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ export const closeBrowser = async function ( { driver } ) {
await quitBrowser( driver );
};

export async function createBrowser() {
const driver = await startBrowser();
export async function createBrowser( { tempDir } ) {
const driver = await startBrowser( { tempDir } );
await ensureNotLoggedIn( driver );
return driver;
}
4 changes: 2 additions & 2 deletions test/e2e/lib/hooks/browser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ export const buildHooks = () => {
let driver;

return {
createBrowser: async function () {
driver = await createBrowser();
createBrowser: async function ( options ) {
driver = await createBrowser( options );
return driver;
},
saveBrowserLogs: function ( options ) {
Expand Down
60 changes: 60 additions & 0 deletions test/e2e/lib/hooks/jest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* External dependencies
*/
import config from 'config';
import path from 'path';
import { mkdtemp, mkdir } from 'fs/promises';
import { getState } from 'expect';

/**
* Internal dependencies
*/
import { buildHooks as buildVideoHooks, isVideoEnabled } from './video';
import { buildHooks as buildBrowserHooks } from './browser';

let tempDir;
let driver;
const startBrowserTimeoutMS = config.get( 'startBrowserTimeoutMS' );
const browserHooks = buildBrowserHooks();
const videoHooks = isVideoEnabled() ? buildVideoHooks() : null;

beforeAll( async () => {
const { testPath } = getState();

const sanitizedTestFilename = path.basename( testPath, path.extname( testPath ) );
const resultsPath = path.join( __dirname, '../../results' );
await mkdir( resultsPath, { recursive: true } );
tempDir = await mkdtemp( path.join( resultsPath, sanitizedTestFilename + '-' ) );

if ( isVideoEnabled() ) {
await videoHooks.startFramebuffer();
await videoHooks.startVideoRecording( { tempDir } );
}

driver = await browserHooks.createBrowser( { tempDir } );
global.__BROWSER__ = driver;
}, startBrowserTimeoutMS );

afterAll( async () => {
if ( isVideoEnabled() ) {
if ( global.__CURRENT_TEST_FAILED__ ) {
// Sometimes chrome crashes mid test. By wraping `takeScreenshot` in a try/catch we ensure
// we at least get a video recording
try {
videoHooks.takeScreenshot( {
tempDir,
testName: global.__CURRENT_TEST_NAME__,
driver: driver,
} );
} catch {}

await videoHooks.saveVideoRecording( { tempDir, testName: global.__CURRENT_TEST_NAME__ } );
}

await videoHooks.stopFramebuffer();
await videoHooks.stopVideoRecording();
}

await browserHooks.saveBrowserLogs( { tempDir } );
await browserHooks.closeBrowser();
} );
2 changes: 1 addition & 1 deletion test/e2e/lib/hooks/mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const mochaHooks = async () => {

hooks.beforeAll.push( async function createBrowserHook() {
this.timeout( startBrowserTimeoutMS );
driver = await browserHooks.createBrowser();
driver = await browserHooks.createBrowser( { tempDir } );
this.driver = driver;
} );

Expand Down
13 changes: 11 additions & 2 deletions test/e2e/lib/hooks/video/video-recorder.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ import { getTestNameWithTime } from '../../test-utils';

const kill = ( proc ) =>
new Promise( ( resolve ) => {
proc.on( 'close', resolve );
proc.kill();
if ( proc.killed || proc.exitCode > 0 ) {
// Already killed
resolve();
} else {
proc.on( 'close', resolve );
proc.kill();
}
} );

export const buildHooks = ( displayNum ) => {
Expand Down Expand Up @@ -46,7 +51,11 @@ export const buildHooks = ( displayNum ) => {
async function saveVideoRecording( { tempDir, testName } ) {
const newFile = path.join( tempDir, `screenshots/${ getTestNameWithTime( testName ) }.mpg` );
await mkdir( path.dirname( newFile ), { recursive: true } );
spawn( 'ls', [ '-la', tempDir + '/screenshots' ], { stdio: 'inherit' } );
await kill( ffVideo );
spawn( 'ls', [ '-la', tempDir + '/screenshots' ], { stdio: 'inherit' } );
spawn( 'ls', [ '-la', file ], { stdio: 'inherit' } );
spawn( 'ls', [ '-la', path.dirname( file ) ], { stdio: 'inherit' } );
await rename( file, newFile );
}

Expand Down
34 changes: 34 additions & 0 deletions test/e2e/lib/jest/jest-environment-e2e.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const JestEnvironmentNode = require( 'jest-environment-node' );

class JestEnvironmentE2E extends JestEnvironmentNode {
testFailed = false;

async handleTestEvent( event ) {
// Top describe (event.describeBlock.name)
// event.type=="run_describe_start" && event.describeBlock.parent.name === "ROOT_DESCRIBE_BLOCK"

// Test (event.test.name)
//

// Save reference to current test
if ( event.name === 'test_start' ) {
this.global.__CURRENT_TEST_NAME__ = event.test.name;
return;
}

// If a hook or a test fails, enter in "failed mode"
if ( event.name === 'hook_failure' || event.name === 'test_fn_failure' ) {
this.global.__CURRENT_TEST_FAILED__ = true;
this.testFailed = true;
return;
}

// If a test starts after a test has already failed, skip it
if ( this.testFailed && event.name === 'test_start' ) {
event.test.mode = 'skip';
return;
}
}
}

module.exports = JestEnvironmentE2E;
17 changes: 17 additions & 0 deletions test/e2e/lib/jest/jest-global-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* External dependencies
*/
import { node } from 'execa';

module.exports = async () => {
// Update chromedriver
const subprocess = node( require.resolve( 'chromedriver/install.js' ), {
env: {
CHROMEDRIVER_SKIP_DOWNLOAD: false,
DETECT_CHROMEDRIVER_VERSION: true,
},
} );
subprocess.stdout.pipe( process.stdout );
subprocess.stderr.pipe( process.stderr );
await subprocess;
};
7 changes: 7 additions & 0 deletions test/e2e/lib/jest/jest-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* External dependencies
*/
import config from 'config';

// Default timeout
jest.setTimeout( config.get( 'jestTimeoutMS' ) );
9 changes: 0 additions & 9 deletions test/e2e/lib/test-utils.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
/**
* External dependencies
*/
import path from 'path';

const BASE_PATH = process.env.TEMP_ASSET_PATH || path.join( __dirname, '..' );

export const generatePath = ( name ) => path.join( BASE_PATH, name );

export const getTestNameWithTime = ( testName ) => {
const currentTestName = testName.replace( /[^a-z0-9]/gi, '-' ).toLowerCase();
const dateTime = new Date().toISOString().split( '.' )[ 0 ].replace( /:/g, '-' );
Expand Down
3 changes: 3 additions & 0 deletions test/e2e/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"esformatter-quotes": "^1.0.3",
"esformatter-semicolons": "^1.1.2",
"esformatter-special-bangs": "^1.0.1",
"eslint-plugin-jest": "^24.3.6",
"esm": "^3.2.25",
"execa": "^5.0.0",
"ffmpeg-static": "^2.4.0",
Expand All @@ -47,6 +48,8 @@
"grunt-cli": "^1.2.0",
"grunt-concurrent": "^2.3.0",
"grunt-shell": "^1.1.2",
"jest-environment-node": "^26.6.2",
"jest-teamcity": "^1.9.0",
"junit-viewer": "^4.9.6",
"lodash": "^4.17.20",
"mailosaur": "^4.0.0",
Expand Down
Loading