diff --git a/circle.yml b/circle.yml index 5e980c4df593..8ad108c7201f 100644 --- a/circle.yml +++ b/circle.yml @@ -179,6 +179,29 @@ commands: path: /tmp/artifacts - store-npm-logs + run-runner-ct-integration-tests: + parameters: + browser: + description: browser shortname to target + type: string + percy: + description: enable percy + type: boolean + default: false + steps: + - attach_workspace: + at: ~/ + - check-conditional-ci + - run: + command: | + cmd=$([[ <> == 'true' ]] && echo 'yarn percy exec --') || true + $cmd yarn workspace @packages/runner-ct run cy:run --browser <> + - store_test_results: + path: /tmp/cypress + - store_artifacts: + path: /tmp/artifacts + - store-npm-logs + run-e2e-tests: parameters: browser: @@ -851,6 +874,13 @@ jobs: - run-runner-integration-tests: browser: firefox + runner-ct-integration-tests-chrome: + <<: *defaults + steps: + - run-runner-ct-integration-tests: + browser: chrome + percy: true + driver-integration-tests-chrome: <<: *defaults parallelism: 5 @@ -1698,6 +1728,9 @@ linux-workflow: &linux-workflow - runner-integration-tests-firefox: requires: - build + - runner-ct-integration-tests-chrome: + requires: + - build ## TODO: add these back in when flaky tests are fixed # - driver-integration-tests-electron: diff --git a/packages/runner-ct/.eslintrc b/packages/runner-ct/.eslintrc new file mode 100644 index 000000000000..896389b435d7 --- /dev/null +++ b/packages/runner-ct/.eslintrc @@ -0,0 +1,46 @@ +{ + "plugins": [ + "cypress", + "@cypress/dev" + ], + "extends": [ + "plugin:@cypress/dev/general", + "plugin:@cypress/dev/tests", + "plugin:@cypress/dev/react" + ], + "parser": "@typescript-eslint/parser", + "env": { + "cypress/globals": true + }, + "rules": { + "react/jsx-filename-extension": [ + "warn", + { + "extensions": [ + ".js", + ".jsx", + ".tsx" + ] + } + ] + }, + "overrides": [ + { + "files": [ + "lib/*" + ], + "rules": { + "no-console": 1 + } + }, + { + "files": [ + "**/*.json" + ], + "rules": { + "quotes": "off", + "comma-dangle": "off" + } + } + ] +} diff --git a/packages/runner-ct/cypress/component/App.spec.tsx b/packages/runner-ct/cypress/component/App.spec.tsx new file mode 100644 index 000000000000..628046116ba3 --- /dev/null +++ b/packages/runner-ct/cypress/component/App.spec.tsx @@ -0,0 +1,88 @@ +/// +import React from 'react' +import { mount } from '@cypress/react' +import App from '../../src/app/app' +import State from '../../src/lib/state' +import '@packages/runner/src/main.scss' + +class FakeEventManager { + start = () => { } + on = () => { } + stop = () => {} + notifyRunningSpec = () => { } +} + +describe('App', () => { + it('renders App', () => { + cy.viewport(1000, 500) + const state = new State({ + reporterWidth: 500, + spec: null, + specs: [{ relative: '/test.js', absolute: 'root/test.js', name: 'test.js' }], + }) + + mount( + , + ) + + cy.percySnapshot() + }) + + context('specs-list resizing', () => { + beforeEach(() => { + cy.viewport(1000, 500) + const state = new State({ + reporterWidth: 500, + spec: null, + specs: [{ relative: '/test.js', absolute: 'root/test.js', name: 'test.js' }], + }) + + mount( + , + ) + }) + + it('closes the spec list when selecting a spec', () => { + cy.get('[data-cy=specs-list-resize-box').should('have.css', 'width', '300px') + + cy.get('[data-cy=resizer]').trigger('mousedown', 'center') + cy.get('[data-cy=resizer]').trigger('mousemove', 'center', { + clientX: 450, + }) + + cy.get('[data-cy=resizer]').trigger('mouseup', 'center') + + cy.get('[data-cy=specs-list-resize-box').should('have.css', 'width', '425px') + }) + + it('restore specs list width after closing and reopen', () => { + cy.get('[data-cy=resizer]').trigger('mousedown', 'center') + cy.get('[data-cy=resizer]').trigger('mousemove', 'center', { + clientX: 500, + }) + + cy.get('[data-cy=resizer]').trigger('mouseup', 'center') + cy.get('[data-cy=specs-list-resize-box').should('have.css', 'width', '475px') + + cy.get('[aria-label="Open the menu"').click() + cy.get('[data-cy=specs-list]').then(([el]) => { + // for some reason should("not.be.visible") doesn't work here so ensure that specs list was outside of screen + expect(el.getBoundingClientRect().x).to.be.lessThan(0) + }) + + cy.get('[aria-label="Open the menu"').click() + + cy.get('[data-cy=specs-list-resize-box').should('have.css', 'width', '475px') + }) + }) +}) diff --git a/packages/runner-ct/cypress/component/SpecList/SpecList.spec.tsx b/packages/runner-ct/cypress/component/SpecList/SpecList.spec.tsx index 7dc3576bc688..a020cd8e4ba6 100644 --- a/packages/runner-ct/cypress/component/SpecList/SpecList.spec.tsx +++ b/packages/runner-ct/cypress/component/SpecList/SpecList.spec.tsx @@ -1,4 +1,3 @@ -/* global cy */ import React from 'react' import { mount } from '@cypress/react' import { SpecList } from '../../../src/SpecList' diff --git a/packages/runner-ct/cypress/plugins/index.js b/packages/runner-ct/cypress/plugins/index.js index 7dc29642b8dc..615067509d11 100644 --- a/packages/runner-ct/cypress/plugins/index.js +++ b/packages/runner-ct/cypress/plugins/index.js @@ -1,17 +1,35 @@ /// -const { startDevServer } = require('@cypress/webpack-dev-server') const path = require('path') +const percyHealthCheck = require('@percy/cypress/task') +const { startDevServer } = require('@cypress/webpack-dev-server') + +function injectStylesInlineForPercyInPlace (webpackConfig) { + webpackConfig.module.rules = webpackConfig.module.rules.map((rule) => { + if (rule?.use[0]?.loader.includes('mini-css-extract-plugin')) { + return { + ...rule, + use: [{ + loader: 'style-loader', + }], + } + } + return rule + }) +} /** * @type {Cypress.PluginConfig} */ module.exports = (on, config) => { + on('task', percyHealthCheck) on('dev-server:start', (options) => { - // yarn tsc webpack.config.ts --esModuleInterop - const config = path.resolve(__dirname, '../../webpack.config.js') + /** @type {import('webpack').Configuration} */ + const { default: webpackConfig } = require(path.resolve(__dirname, '..', '..', 'webpack.config.ts')) + + injectStylesInlineForPercyInPlace(webpackConfig) return startDevServer({ - webpackConfig: require(config).default, + webpackConfig, options, }) }) diff --git a/packages/runner-ct/cypress/support/index.js b/packages/runner-ct/cypress/support/index.js index d68db96df269..dff0c28b43d8 100644 --- a/packages/runner-ct/cypress/support/index.js +++ b/packages/runner-ct/cypress/support/index.js @@ -15,6 +15,7 @@ // Import commands.js using ES2015 syntax: import './commands' +import '@percy/cypress' // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/packages/runner-ct/package.json b/packages/runner-ct/package.json index 08e73e5fae71..58c78ba4037d 100644 --- a/packages/runner-ct/package.json +++ b/packages/runner-ct/package.json @@ -8,6 +8,8 @@ "build": "webpack", "build-prod": "cross-env NODE_ENV=production yarn build && tsc", "clean-deps": "rm -rf node_modules", + "cy:open": "node ../../scripts/cypress.js open-ct --project ${PWD}", + "cy:run": "node ../../scripts/cypress.js run-ct --project ${PWD}", "postinstall": "echo '@packages/runner needs: yarn build'", "test-unit": "ts-mocha --config ./test/.mocharc.js --require './test/setup.js'", "watch": "webpack --watch --progress --config webpack.config.ts" @@ -40,6 +42,7 @@ "@babel/core": "^7.12.3", "@babel/preset-env": "^7.12.1", "@packages/driver": "0.0.0-development", + "@percy/cypress": "2.3.4", "@types/sockjs-client": "1.1.0", "babel-loader": "8.1.0", "clean-webpack-plugin": "^3.0.0", diff --git a/packages/runner-ct/render-target.js b/packages/runner-ct/render-target.js index f7e40e596293..433e465fdad2 100644 --- a/packages/runner-ct/render-target.js +++ b/packages/runner-ct/render-target.js @@ -1,4 +1,3 @@ -/* globals document */ import $ from 'cash-dom' function appendTargetIfNotExists (id, tag = 'div', parent = document.body) { diff --git a/packages/runner-ct/src/SpecList/SpecList.tsx b/packages/runner-ct/src/SpecList/SpecList.tsx index 13015e729f0f..c42adf6c8a52 100644 --- a/packages/runner-ct/src/SpecList/SpecList.tsx +++ b/packages/runner-ct/src/SpecList/SpecList.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react' +import cs from 'classnames' import { SpecItem } from './SpecItem' import { OnSelectSpec } from './SpecFileItem' import { SearchSpec } from './components/SearchSpec' @@ -10,6 +11,7 @@ interface SpecsListProps { selectedSpecs: string[] specs: Cypress.Cypress['spec'][] onSelectSpec: OnSelectSpec + disableTextSelection: boolean } export const SpecList: React.FC = (props) => { @@ -23,7 +25,10 @@ export const SpecList: React.FC = (props) => { value={search} onSearch={setSearch} /> -
    +
      { hierarchy.map((item) => (