From 2a4e84ec66e0cc5c3a9bc673a87c545bba721a76 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Thu, 1 Apr 2021 06:28:01 -0700 Subject: [PATCH 1/6] docs(component-testing): Added rerender/props updating recipe --- npm/react/docs/recipes.md | 116 +++++++++++++++++++++++++++++++++----- 1 file changed, 101 insertions(+), 15 deletions(-) diff --git a/npm/react/docs/recipes.md b/npm/react/docs/recipes.md index b8ada21c9612..b894a50f04b3 100644 --- a/npm/react/docs/recipes.md +++ b/npm/react/docs/recipes.md @@ -1,22 +1,28 @@ # Recipes - [Recipes](#recipes) - - [Do nothing](#do-nothing) - - [React Scripts](#react-scripts) - - [Next.js](#nextjs) - - [Your webpack config](#your-webpack-config) - - [Your `.babelrc` file](#your-babelrc-file) - - [Add Babel plugins](#add-babel-plugins) + - [Configuration](#configuration) + - [Do nothing](#do-nothing) + - [React Scripts](#react-scripts) + - [Next.js](#nextjs) + - [Your Webpack config](#your-webpack-config) + - [Your `.babelrc` file](#your-babelrc-file) + - [Add Babel plugins](#add-babel-plugins) + - [Using rollup config](#using-rollup-config) + - [Usage](#usage) + - [Changing props](#changing-props) -## Do nothing +## Configuration + +### Do nothing Cypress Test Runner understands plain JSX by default, so for simple React applications it ... might just test components right out of the box! But usually you want to point Cypress at your application's current Webpack configuration, so the specs can import your components correctly. The next recipes discuss common ways for doing this. -## React Scripts +### React Scripts -If you are using Create-React-App v3 or `react-scripts`, and want to reuse the built in webpack (even after ejecting), this module ships with Cypress preprocessor in [plugins](plugins) folder. +If you are using Create-React-App v3 or `react-scripts`, and want to reuse the built in Webpack (even after ejecting), this module ships with Cypress preprocessor in [plugins](plugins) folder. ```js // cypress/plugins/index.js @@ -32,7 +38,7 @@ See example repo [bahmutov/try-cra-with-unit-test](https://github.com/bahmutov/t **Tip:** `plugins/react-scripts` is just loading `plugins/cra-v3`. -## Next.js +### Next.js ```js // cypress/plugins/index.js @@ -46,9 +52,9 @@ module.exports = (on, config) => { See example in the folder [examples/nextjs](examples/nextjs). -## Your webpack config +### Your Webpack config -If you have your own webpack config, you can use included plugins file to load it. You can pass the webpack config file name (with respect to the root folder where `cypress.json` file sits) via plugins file or via an `env` variable in `cypress.json` +If you have your own Webpack config, you can use included plugins file to load it. You can pass the Webpack config file name (with respect to the root folder where `cypress.json` file sits) via plugins file or via an `env` variable in `cypress.json` ```js // cypress/plugins/index.js @@ -64,7 +70,7 @@ module.exports = (on, config) => { See example in [bahmutov/Jscrambler-Webpack-React](https://github.com/bahmutov/Jscrambler-Webpack-React) or included example in the folder [examples/webpack-file](examples/webpack-file). -## Your `.babelrc` file +### Your `.babelrc` file If you are using Babel without Webpack to transpile, you can use the plugin that tells Babel loader to use your `.babelrc` configuration file. @@ -81,7 +87,7 @@ module.exports = (on, config) => { See example in the folder [examples/using-babel](examples/using-babel) and [examples/using-babel-typescript](examples/using-babel-typescript). -### Add Babel plugins +#### Add Babel plugins If you want to use code instrumentation, add the [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul) to your `.babelrc` setup. You do not even need to install it separately, as it is already included in `@cypress/react` as a dependency. @@ -125,7 +131,7 @@ When loading your `.babelrc` settings, `@cypress/react` sets `BABEL_ENV` and `NO See [examples/using-babel](examples/using-babel) folder for full example. -### Using rollup config +#### Using rollup config If you are using rollup for bundling – we can use it as well for the bundling. Check the example: @@ -158,3 +164,83 @@ replace({ 'process.env.NODE_ENV': JSON.stringify('development') }), ``` See [examples/rollup](examples/rollup) folder for full example. + +## Usage + +### Changing props + +Many components have some statefulness, whether explicitly through `useState`, or implicitly through `useEffect`. Therefore during testing it is useful to keep the component mounted, but change the props being passed to it in order to preserve its state. + +We recommend building a "wrapper" component with the necessary DOM controls to push new props to your component under test. + +```js +const Accumulator = ({ value }) => { + const [storedValues, setStoredValues] = React.useState([]) + + React.useEffect(() => { + if (!value) { + return + } + + setStoredValues((prev) => [...prev, value]) + }, [value]) + + return ( + + ) +} +``` + +This component is an accumulator that stores each `value` prop passed to it. We create a wrapper component that has an `input` and a `button` to push new values to the `value` prop. + +```js +const Wrapper = () => { + const ref = React.useRef() + const [value, setValue] = React.useState() + + return ( +
+ + + +
+ ) +} +``` + +With this, we can begin writing component tests to check the functionality of our `Accumulator` component. + +```js +it('should store value', () => { + mount() + + cy.get('input').type('Component testing is awesome!') + cy.get('button').click() + + cy.get('li').eq(0).contains('Component testing is awesome!') + + cy.get('input').clear().type('We are dynamically changing props') + cy.get('button').click() + + cy.get('li').eq(1).contains('We are dynamically changing props') + + cy.get('input').clear().type('to build a list of text') + cy.get('button').click() + + cy.get('li').eq(0).contains('Component testing is awesome!') + cy.get('li').eq(1).contains('We are dynamically changing props') + cy.get('li').eq(2).contains('to build a list of text') +}) +``` \ No newline at end of file From 358628f3b353e6a4f6d88d44f4fb6a403155c129 Mon Sep 17 00:00:00 2001 From: Jennifer Shehane Date: Fri, 2 Apr 2021 14:02:42 -0400 Subject: [PATCH 2/6] chore: Turn on test retries in run mode for other packages (#15714) Co-authored-by: Zach Panzarino --- packages/reporter/cypress.json | 4 ++++ packages/runner/cypress.json | 6 +++++- .../runner/cypress/integration/retries.mochaEvents.spec.js | 2 +- .../runner/cypress/integration/runner.mochaEvents.spec.js | 2 +- .../runner/cypress/integration/studio.mochaEvents.spec.js | 4 ++-- packages/ui-components/cypress.json | 4 ++++ 6 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/reporter/cypress.json b/packages/reporter/cypress.json index 4b04ce7856dc..bbf940fae4c6 100644 --- a/packages/reporter/cypress.json +++ b/packages/reporter/cypress.json @@ -6,5 +6,9 @@ "reporter": "../../node_modules/cypress-multi-reporters/index.js", "reporterOptions": { "configFile": "../../mocha-reporter-config.json" + }, + "retries": { + "runMode": 2, + "openMode": 0 } } diff --git a/packages/runner/cypress.json b/packages/runner/cypress.json index 0ddefb02cf0f..745c5c9e81c1 100644 --- a/packages/runner/cypress.json +++ b/packages/runner/cypress.json @@ -1,4 +1,8 @@ { "projectId": "ypt4pf", - "baseUrl": "http://localhost:3500" + "baseUrl": "http://localhost:3500", + "retries": { + "runMode": 2, + "openMode": 0 + } } diff --git a/packages/runner/cypress/integration/retries.mochaEvents.spec.js b/packages/runner/cypress/integration/retries.mochaEvents.spec.js index 84f66fe10f03..89ebf87add60 100644 --- a/packages/runner/cypress/integration/retries.mochaEvents.spec.js +++ b/packages/runner/cypress/integration/retries.mochaEvents.spec.js @@ -18,7 +18,7 @@ const threeTestsWithRetry = { }, } -describe('src/cypress/runner retries mochaEvents', () => { +describe('src/cypress/runner retries mochaEvents', { retries: 0 }, () => { // NOTE: for test-retries it('can set retry config', () => { runIsolatedCypress({}, { config: { retries: 1 } }) diff --git a/packages/runner/cypress/integration/runner.mochaEvents.spec.js b/packages/runner/cypress/integration/runner.mochaEvents.spec.js index 80a4902ca560..d176d7cdc27e 100644 --- a/packages/runner/cypress/integration/runner.mochaEvents.spec.js +++ b/packages/runner/cypress/integration/runner.mochaEvents.spec.js @@ -13,7 +13,7 @@ const threeTestsWithHooks = { suites: { 'suite 1': { hooks: ['before', 'beforeEach', 'afterEach', 'after'], tests: ['test 1', 'test 2', 'test 3'] } }, } -describe('src/cypress/runner', () => { +describe('src/cypress/runner', { retries: 0 }, () => { describe('tests finish with correct state', () => { describe('hook failures', () => { it('fail in [before]', () => { diff --git a/packages/runner/cypress/integration/studio.mochaEvents.spec.js b/packages/runner/cypress/integration/studio.mochaEvents.spec.js index e53df53f2a62..44f4c0f05959 100644 --- a/packages/runner/cypress/integration/studio.mochaEvents.spec.js +++ b/packages/runner/cypress/integration/studio.mochaEvents.spec.js @@ -1,9 +1,9 @@ const helpers = require('../support/helpers') const { createCypress } = helpers -const { runIsolatedCypress, snapshotMochaEvents } = createCypress({ config: { experimentalStudio: true, isTextTerminal: true } }) +const { runIsolatedCypress, snapshotMochaEvents } = createCypress({ config: { experimentalStudio: true, isTextTerminal: true, retries: 0 } }) -describe('studio mocha events', () => { +describe('studio mocha events', { retries: 0 }, () => { it('only runs a single test by id', () => { runIsolatedCypress('cypress/fixtures/studio/basic_spec.js', { state: { diff --git a/packages/ui-components/cypress.json b/packages/ui-components/cypress.json index 130fd5c82f22..88a0a5a24700 100644 --- a/packages/ui-components/cypress.json +++ b/packages/ui-components/cypress.json @@ -4,5 +4,9 @@ "reporter": "../../node_modules/cypress-multi-reporters/index.js", "reporterOptions": { "configFile": "../../mocha-reporter-config.json" + }, + "retries": { + "runMode": 2, + "openMode": 0 } } From 97a54e1b4e3e285d1e5af49d762127f376bccd09 Mon Sep 17 00:00:00 2001 From: Adam Gastineau Date: Fri, 2 Apr 2021 13:12:43 -0700 Subject: [PATCH 3/6] Incorporated rerender comments --- npm/react/docs/recipes.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/npm/react/docs/recipes.md b/npm/react/docs/recipes.md index b894a50f04b3..6a476309d3a9 100644 --- a/npm/react/docs/recipes.md +++ b/npm/react/docs/recipes.md @@ -169,9 +169,9 @@ See [examples/rollup](examples/rollup) folder for full example. ### Changing props -Many components have some statefulness, whether explicitly through `useState`, or implicitly through `useEffect`. Therefore during testing it is useful to keep the component mounted, but change the props being passed to it in order to preserve its state. +Many components have some statefulness, whether explicitly through `useState`, or implicitly through `useEffect`. Therefore during testing it is useful to keep the component mounted, but change the props being passed to it in order to preserve its state. This is referred to in some testing frameworks as `rerender()`. -We recommend building a "wrapper" component with the necessary DOM controls to push new props to your component under test. +We recommend building a "wrapper" component that acts similarly to how your users will interact with the component under test. In isolation, you can add DOM controls to push new props to your component. ```js const Accumulator = ({ value }) => { @@ -200,7 +200,7 @@ const Accumulator = ({ value }) => { This component is an accumulator that stores each `value` prop passed to it. We create a wrapper component that has an `input` and a `button` to push new values to the `value` prop. ```js -const Wrapper = () => { +const TestAcc = () => { const ref = React.useRef() const [value, setValue] = React.useState() @@ -224,7 +224,7 @@ With this, we can begin writing component tests to check the functionality of ou ```js it('should store value', () => { - mount() + mount() cy.get('input').type('Component testing is awesome!') cy.get('button').click() From 88a3830d68ef71290ecad3ab7ba440370f314741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Barth=C3=A9l=C3=A9my=20Ledoux?= Date: Fri, 2 Apr 2021 22:02:00 -0500 Subject: [PATCH 4/6] fix(vite-dev-server): import cypress client script asynchronously to avoid flake (#15778) * fix: load script test last to avoid flake * chore: comment loading the cypress client last --- npm/vite-dev-server/src/makeCypressPlugin.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/npm/vite-dev-server/src/makeCypressPlugin.ts b/npm/vite-dev-server/src/makeCypressPlugin.ts index 86ddcb258119..0e632d6e2e06 100644 --- a/npm/vite-dev-server/src/makeCypressPlugin.ts +++ b/npm/vite-dev-server/src/makeCypressPlugin.ts @@ -34,9 +34,13 @@ export const makeCypressPlugin = ( }, transformIndexHtml () { return [ + // load the script at the end of the body + // script has to be loaded when the vite client is connected { tag: 'script', - attrs: { type: 'module', src: INIT_FILEPATH }, + injectTo: 'body', + attrs: { type: 'module' }, + children: `import(${JSON.stringify(INIT_FILEPATH)})`, }, ] }, From 84bfe2e16dfd03f1490551ad841f700ad2a010cc Mon Sep 17 00:00:00 2001 From: Dmitriy Kovalenko Date: Mon, 5 Apr 2021 06:36:20 +0300 Subject: [PATCH 5/6] chore(create-cypress-test): Fix a bunch of small issues in wizard (#15739) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Install correct version for vue 3 * Disable users config * Disable babel.config.js * Avoid users babel.config files in wizard * Cleanup Co-authored-by: Barthélémy Ledoux Co-authored-by: Lachlan Miller --- .../component-testing/babel/babelTransform.ts | 11 ++- .../init-component-testing.test.ts | 72 ++++++++++++++++++- .../installFrameworkAdapter.ts | 5 +- .../templates/_shared/vite.ts | 2 +- .../component-testing/templates/vue/vueCli.ts | 2 +- .../src/findPackageJson.ts | 12 +++- npm/create-cypress-tests/src/main.ts | 2 +- 7 files changed, 93 insertions(+), 13 deletions(-) diff --git a/npm/create-cypress-tests/src/component-testing/babel/babelTransform.ts b/npm/create-cypress-tests/src/component-testing/babel/babelTransform.ts index a63dd4204a38..22e5858dca3a 100644 --- a/npm/create-cypress-tests/src/component-testing/babel/babelTransform.ts +++ b/npm/create-cypress-tests/src/component-testing/babel/babelTransform.ts @@ -18,6 +18,13 @@ function tryRequirePrettier () { return null } } +const sharedBabelOptions = { + // disable user config + configFile: false, + babelrc: false, + presets: [], + root: process.env.BABEL_TEST_ROOT, // for testing +} async function transformFileViaPlugin (filePath: string, babelPlugin: babel.PluginObj) { try { @@ -27,7 +34,7 @@ async function transformFileViaPlugin (filePath: string, babelPlugin: babel.Plug filename: path.basename(filePath), filenameRelative: path.relative(process.cwd(), filePath), plugins: [babelPlugin], - presets: [], + ...sharedBabelOptions, }) if (!updatedResult) { @@ -127,7 +134,7 @@ export async function getPluginsSourceExample (ast: PluginsConfigAst) { const babelResult = await babel.transformAsync(exampleCode, { filename: 'nothing.js', plugins: [createTransformPluginsFileBabelPlugin(ast)], - presets: [], + ...sharedBabelOptions, }) if (!babelResult?.code) { diff --git a/npm/create-cypress-tests/src/component-testing/init-component-testing.test.ts b/npm/create-cypress-tests/src/component-testing/init-component-testing.test.ts index 81f41f482217..d8593362ba3e 100644 --- a/npm/create-cypress-tests/src/component-testing/init-component-testing.test.ts +++ b/npm/create-cypress-tests/src/component-testing/init-component-testing.test.ts @@ -32,6 +32,8 @@ describe('init component tests script', () => { await fs.remove(e2eTestOutputPath) await fs.mkdir(e2eTestOutputPath) + + process.env.BABEL_TEST_ROOT = e2eTestOutputPath }) afterEach(() => { @@ -170,7 +172,7 @@ describe('init component tests script', () => { createTempFiles({ '/cypress.json': '{}', '/webpack.config.js': 'module.exports = { }', - '/package.json': JSON.stringify({ dependencies: { react: '*', vue: '*' } }), + '/package.json': JSON.stringify({ dependencies: { react: '*', vue: '^2.4.5' } }), }) promptSpy = sinon.stub(inquirer, 'prompt') @@ -187,12 +189,44 @@ describe('init component tests script', () => { await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true }) expect( - someOfSpyCallsIncludes(global.console.log, `It looks like all these frameworks: ${chalk.yellow('react, vue')} are available from this directory.`), + someOfSpyCallsIncludes(global.console.log, `It looks like all these frameworks: ${chalk.yellow('react, vue@2')} are available from this directory.`), ).to.be.true }) - it('installs the right adapter', () => { + it('installs the right adapter', async () => { + createTempFiles({ + '/cypress.json': '{}', + '/webpack.config.js': 'module.exports = { }', + '/package.json': JSON.stringify({ dependencies: { react: '16.4.5' } }), + }) + + promptSpy = sinon.stub(inquirer, 'prompt') + .onCall(0) + .returns(Promise.resolve({ + chosenTemplateName: 'vite', + componentFolder: 'src', + }) as any) + await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true }) + expect(execStub).to.be.calledWith('yarn add @cypress/react --dev') + }) + + it('installs the right adapter for vue 3', async () => { + createTempFiles({ + '/cypress.json': '{}', + '/vite.config.js': 'module.exports = { }', + '/package.json': JSON.stringify({ dependencies: { vue: '^3.0.0' } }), + }) + + promptSpy = sinon.stub(inquirer, 'prompt') + .onCall(0) + .returns(Promise.resolve({ + chosenTemplateName: 'vite', + componentFolder: 'src', + }) as any) + + await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true }) + expect(execStub).to.be.calledWith('yarn add @cypress/vue@3 --dev') }) it('suggest the right instruction based on user template choice', async () => { @@ -272,4 +306,36 @@ describe('init component tests script', () => { ), ).to.be.true }) + + it('Doesn\'t affect injected code if user has custom babel.config.js', async () => { + createTempFiles({ + '/cypress/plugins/index.js': 'module.exports = (on, config) => {}', + '/cypress.json': '{}', + 'babel.config.js': `module.exports = ${JSON.stringify({ + presets: [ + '@babel/preset-env', + ], + })}`, + '/package.json': JSON.stringify({ + dependencies: { + babel: '*', + react: '^16.0.0', + }, + }), + }) + + sinon.stub(inquirer, 'prompt').returns(Promise.resolve({ + chosenTemplateName: 'create-react-app', + componentFolder: 'cypress/component', + }) as any) + + await initComponentTesting({ config: {}, cypressConfigPath, useYarn: true }) + const babelPluginsOutput = await fs.readFile( + path.join(e2eTestOutputPath, 'cypress', 'plugins', 'index.js'), + 'utf-8', + ) + + expect(babelPluginsOutput).not.to.contain('use strict') + expect(babelPluginsOutput).to.contain('module.exports = (on, config) => {') + }) }) diff --git a/npm/create-cypress-tests/src/component-testing/installFrameworkAdapter.ts b/npm/create-cypress-tests/src/component-testing/installFrameworkAdapter.ts index c08f0074bc50..d93325f8be33 100644 --- a/npm/create-cypress-tests/src/component-testing/installFrameworkAdapter.ts +++ b/npm/create-cypress-tests/src/component-testing/installFrameworkAdapter.ts @@ -6,8 +6,9 @@ import { installDependency } from '../utils' async function guessOrAskForFramework (cwd: string): Promise<'react' | 'vue'> { // please sort this alphabetically const frameworks = { - react: () => scanFSForAvailableDependency(cwd, ['react', 'react-dom']), - vue: () => scanFSForAvailableDependency(cwd, ['vue']), + react: () => scanFSForAvailableDependency(cwd, { react: '*', 'react-dom': '*' }), + 'vue@2': () => scanFSForAvailableDependency(cwd, { vue: '2.x' }), + 'vue@3': () => scanFSForAvailableDependency(cwd, { vue: '3.x' }), } const guesses = Object.keys(frameworks).filter((framework) => { diff --git a/npm/create-cypress-tests/src/component-testing/templates/_shared/vite.ts b/npm/create-cypress-tests/src/component-testing/templates/_shared/vite.ts index c705fce55314..5d7b306fdd31 100644 --- a/npm/create-cypress-tests/src/component-testing/templates/_shared/vite.ts +++ b/npm/create-cypress-tests/src/component-testing/templates/_shared/vite.ts @@ -20,7 +20,7 @@ export const ViteTemplate: Template = { }, test: (root) => { return { - success: scanFSForAvailableDependency(root, ['vite']), + success: scanFSForAvailableDependency(root, { vite: '*' }), } }, } diff --git a/npm/create-cypress-tests/src/component-testing/templates/vue/vueCli.ts b/npm/create-cypress-tests/src/component-testing/templates/vue/vueCli.ts index 94efc1a284d0..40be5ec2ce90 100644 --- a/npm/create-cypress-tests/src/component-testing/templates/vue/vueCli.ts +++ b/npm/create-cypress-tests/src/component-testing/templates/vue/vueCli.ts @@ -20,7 +20,7 @@ export const VueCliTemplate: Template = { } }, test: (root) => { - const hasVueCliService = scanFSForAvailableDependency(root, ['@vue/cli-service']) + const hasVueCliService = scanFSForAvailableDependency(root, { '@vue/cli-service': '>=4' }) return { success: hasVueCliService, diff --git a/npm/create-cypress-tests/src/findPackageJson.ts b/npm/create-cypress-tests/src/findPackageJson.ts index f52fcdd52fa6..715587bcbc1d 100644 --- a/npm/create-cypress-tests/src/findPackageJson.ts +++ b/npm/create-cypress-tests/src/findPackageJson.ts @@ -1,6 +1,7 @@ import path from 'path' import fs from 'fs' import findUp from 'find-up' +import { validateSemverVersion } from './utils' type PackageJsonLike = { name?: string @@ -91,7 +92,7 @@ export function createFindPackageJsonIterator (rootPath = process.cwd()) { } } -export function scanFSForAvailableDependency (cwd: string, deps: string[]) { +export function scanFSForAvailableDependency (cwd: string, lookingForDeps: Record) { const { success } = createFindPackageJsonIterator(cwd) .map(({ dependencies, devDependencies }, path) => { if (!dependencies && !devDependencies) { @@ -99,8 +100,13 @@ export function scanFSForAvailableDependency (cwd: string, deps: string[]) { } return { - success: Object.keys({ ...dependencies, ...devDependencies }) - .some((dependency) => deps.includes(dependency)), + success: Object.entries({ ...dependencies, ...devDependencies }) + .some(([dependency, version]) => { + return ( + Boolean(lookingForDeps[dependency]) + && validateSemverVersion(version, lookingForDeps[dependency], dependency) + ) + }), } }) diff --git a/npm/create-cypress-tests/src/main.ts b/npm/create-cypress-tests/src/main.ts index cfc52548765a..d836476591fe 100644 --- a/npm/create-cypress-tests/src/main.ts +++ b/npm/create-cypress-tests/src/main.ts @@ -38,7 +38,7 @@ async function shouldUseYarn () { } function shouldUseTypescript () { - return scanFSForAvailableDependency(process.cwd(), ['typescript']) + return scanFSForAvailableDependency(process.cwd(), { typescript: '*' }) } async function askForComponentTesting () { From 1eb32cd1a0d040697788994aebccd4e153355115 Mon Sep 17 00:00:00 2001 From: Chris Breiding Date: Mon, 5 Apr 2021 12:44:27 -0400 Subject: [PATCH 6/6] fix: restore previous electron window size in interactive mode (#15789) --- packages/server/lib/browsers/electron.js | 26 ++++++----- .../test/unit/browsers/electron_spec.js | 46 ++++++++++++++----- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/packages/server/lib/browsers/electron.js b/packages/server/lib/browsers/electron.js index 8224eac44970..4de1b2128b40 100644 --- a/packages/server/lib/browsers/electron.js +++ b/packages/server/lib/browsers/electron.js @@ -164,22 +164,23 @@ module.exports = { _getAutomation, - _render (url, projectRoot, automation, options = {}) { - const win = Windows.create(projectRoot, options) + _render (url, automation, preferences = {}, options = {}) { + const win = Windows.create(options.projectRoot, preferences) - if (options.browser.isHeadless) { + if (preferences.browser.isHeadless) { // seemingly the only way to force headless to a certain screen size - // electron BrowserWindow constructor is not respecting width/height options - win.setSize(options.width, options.height) - } else { - // we maximize in headed mode, this is consistent with chrome+firefox headed + // electron BrowserWindow constructor is not respecting width/height preferences + win.setSize(preferences.width, preferences.height) + } else if (options.isTextTerminal) { + // we maximize in headed mode as long as it's run mode + // this is consistent with chrome+firefox headed win.maximize() } - automation.use(_getAutomation(win, options)) + automation.use(_getAutomation(win, preferences)) - return this._launch(win, url, automation, options) - .tap(_maybeRecordVideo(win.webContents, options)) + return this._launch(win, url, automation, preferences) + .tap(_maybeRecordVideo(win.webContents, preferences)) }, _launchChild (e, url, parent, projectRoot, state, options, automation) { @@ -403,7 +404,10 @@ module.exports = { debug('launching browser window to url: %s', url) - return this._render(url, projectRoot, automation, preferences) + return this._render(url, automation, preferences, { + projectRoot: options.projectRoot, + isTextTerminal: options.isTextTerminal, + }) .then(async (win) => { await _installExtensions(win, launchOptions.extensions, options) diff --git a/packages/server/test/unit/browsers/electron_spec.js b/packages/server/test/unit/browsers/electron_spec.js index a9199fccb04c..572131cd8f5a 100644 --- a/packages/server/test/unit/browsers/electron_spec.js +++ b/packages/server/test/unit/browsers/electron_spec.js @@ -82,13 +82,18 @@ describe('lib/browsers/electron', () => { options = Windows.defaults(options) - const keys = _.keys(electron._render.firstCall.args[3]) + const preferencesKeys = _.keys(electron._render.firstCall.args[2]) - expect(_.keys(options)).to.deep.eq(keys) + expect(_.keys(options)).to.deep.eq(preferencesKeys) + + expect(electron._render.firstCall.args[3]).to.deep.eql({ + projectRoot: this.options.projectRoot, + isTextTerminal: this.options.isTextTerminal, + }) expect(electron._render).to.be.calledWith( this.url, - this.options.projectRoot, + this.automation, ) }) }) @@ -112,7 +117,7 @@ describe('lib/browsers/electron', () => { return electron.open('electron', this.url, this.options, this.automation) .then(() => { - const options = electron._render.firstCall.args[3] + const options = electron._render.firstCall.args[2] expect(options).to.include.keys('onFocus', 'onNewWindow', 'onCrashed') }) @@ -129,7 +134,7 @@ describe('lib/browsers/electron', () => { return electron.open('electron', this.url, this.options, this.automation) .then(() => { - const options = electron._render.firstCall.args[3] + const options = electron._render.firstCall.args[2] expect(options).to.include.keys('foo', 'onFocus', 'onNewWindow', 'onCrashed') }) @@ -286,6 +291,8 @@ describe('lib/browsers/electron', () => { setSize: sinon.stub(), } + this.preferences = { ...this.options } + sinon.stub(menu, 'set') sinon.stub(electron, '_setProxy').resolves() sinon.stub(electron, '_launch').resolves() @@ -295,29 +302,46 @@ describe('lib/browsers/electron', () => { }) it('creates window instance and calls launch with window', function () { - return electron._render(this.url, this.options.projectRoot, this.automation, this.options) + return electron._render(this.url, this.automation, this.preferences, this.options) .then(() => { expect(Windows.create).to.be.calledWith(this.options.projectRoot, this.options) - expect(this.newWin.maximize).called expect(this.newWin.setSize).not.called - expect(electron._launch).to.be.calledWith(this.newWin, this.url, this.automation, this.options) + expect(electron._launch).to.be.calledWith(this.newWin, this.url, this.automation, this.preferences) }) }) it('calls setSize on electron window if headless', function () { - const options = { ...this.options, browser: { isHeadless: true }, width: 100, height: 200 } + const preferences = { ...this.preferences, browser: { isHeadless: true }, width: 100, height: 200 } - return electron._render(this.url, this.options.projectRoot, this.automation, options) + return electron._render(this.url, this.automation, preferences, this.options) .then(() => { expect(this.newWin.maximize).not.called expect(this.newWin.setSize).calledWith(100, 200) }) }) + it('maximizes electron window if headed and not interactive', function () { + this.options.isTextTerminal = true + + return electron._render(this.url, this.automation, this.preferences, this.options) + .then(() => { + expect(this.newWin.maximize).to.be.called + }) + }) + + it('does not maximize electron window if interactive', function () { + this.options.isTextTerminal = false + + return electron._render(this.url, this.automation, this.preferences, this.options) + .then(() => { + expect(this.newWin.maximize).not.to.be.called + }) + }) + it('registers onRequest automation middleware', function () { sinon.spy(this.automation, 'use') - return electron._render(this.url, this.options.projectRoot, this.automation, this.options) + return electron._render(this.url, this.automation, this.preferences, this.options) .then(() => { expect(Windows.create).to.be.calledWith(this.options.projectRoot, this.options) expect(this.automation.use).to.be.called