diff --git a/browser-versions.json b/browser-versions.json index d04f9287fd3a..5289b951584f 100644 --- a/browser-versions.json +++ b/browser-versions.json @@ -1,4 +1,4 @@ { - "chrome:beta": "91.0.4472.57", + "chrome:beta": "91.0.4472.69", "chrome:stable": "90.0.4430.212" } diff --git a/circle.yml b/circle.yml index c10c458189aa..72dbe41359ba 100644 --- a/circle.yml +++ b/circle.yml @@ -1353,7 +1353,14 @@ jobs: command: yarn workspace @cypress/design-system build - run: name: Run tests - command: yarn test --reporter cypress-circleci-reporter --reporter-options resultsDir=./test_results + # will use PERCY_TOKEN environment variable if available + command: | + CYPRESS_KONFIG_ENV=production \ + PERCY_PARALLEL_NONCE=$CIRCLE_WORKFLOW_ID \ + PERCY_ENABLE=${PERCY_TOKEN:-0} \ + PERCY_PARALLEL_TOTAL=-1 \ + yarn percy exec --parallel -- -- \ + yarn test --reporter cypress-circleci-reporter --reporter-options resultsDir=./test_results working_directory: npm/design-system - store_test_results: path: npm/design-system/test_results diff --git a/npm/design-system/cypress.json b/npm/design-system/cypress.json index 65f6885eb73c..00494ad9b75c 100644 --- a/npm/design-system/cypress.json +++ b/npm/design-system/cypress.json @@ -1,6 +1,6 @@ { - "viewportWidth": 400, - "viewportHeight": 400, + "viewportWidth": 1024, + "viewportHeight": 800, "video": false, "projectId": "z9dxah", "testFiles": "**/*spec.{js,jsx,ts,tsx}", @@ -13,4 +13,4 @@ ], "componentFolder": "src", "fixturesFolder": false -} \ No newline at end of file +} diff --git a/npm/design-system/cypress/support/index.js b/npm/design-system/cypress/support/index.js index c5ecc8a83e5f..1ae415651555 100644 --- a/npm/design-system/cypress/support/index.js +++ b/npm/design-system/cypress/support/index.js @@ -1,5 +1,6 @@ import 'regenerator-runtime/runtime' import 'cypress-real-events/support' +import '@percy/cypress' import './storybook' // Need to register these once per app. Depending which components are consumed diff --git a/npm/design-system/src/components/Nav/LeftNav.spec.tsx b/npm/design-system/src/components/Nav/LeftNav.spec.tsx index 820e4e319e2d..96eeaf539351 100644 --- a/npm/design-system/src/components/Nav/LeftNav.spec.tsx +++ b/npm/design-system/src/components/Nav/LeftNav.spec.tsx @@ -4,6 +4,7 @@ import { library } from '@fortawesome/fontawesome-svg-core' import { fab } from '@fortawesome/free-brands-svg-icons' import { fas } from '@fortawesome/free-solid-svg-icons' +import { mountAndSnapshot } from 'util/testing' import { NavItem } from './types' import { LeftNav } from './LeftNav' @@ -43,7 +44,7 @@ describe('LeftNav', () => { }) it('renders a stack of items', () => { - mount() + mountAndSnapshot() cy.get('nav').should('exist') }) diff --git a/npm/design-system/src/components/fileTree/FileTree.spec.tsx b/npm/design-system/src/components/fileTree/FileTree.spec.tsx index ecb30fccb776..c3c193cbf9be 100644 --- a/npm/design-system/src/components/fileTree/FileTree.spec.tsx +++ b/npm/design-system/src/components/fileTree/FileTree.spec.tsx @@ -1,6 +1,7 @@ import React from 'react' import { mount } from '@cypress/react' -import { FileTree } from './index' +import { FileTree } from './FileTree' +import { mountAndSnapshot } from 'util/testing' const files = [ { @@ -36,7 +37,7 @@ describe('FileTree', () => { it('should send onFilePress callback on space and enter', () => { const filePressStub = cy.stub() - mount( + mountAndSnapshot(
, @@ -118,6 +119,8 @@ describe('FileTree', () => { cy.get('[data-cy=virtualized-tree] > div').scrollTo('bottom') + cy.contains('.treeChild', 'File 99').should('be.visible') + cy.get('[data-cy=virtualized-tree]').focus().type('{downarrow}').type('{downarrow}') cy.contains('.treeChild', 'File 3').should('be.visible') diff --git a/npm/design-system/src/components/searchInput/SearchInput.spec.tsx b/npm/design-system/src/components/searchInput/SearchInput.spec.tsx index c5fd9163040c..a91f97c649ab 100644 --- a/npm/design-system/src/components/searchInput/SearchInput.spec.tsx +++ b/npm/design-system/src/components/searchInput/SearchInput.spec.tsx @@ -3,6 +3,7 @@ import { mount } from '@cypress/react' import { SearchInput } from './SearchInput' import { useCallback, useState } from 'react' +import { mountAndSnapshot } from 'util/testing' describe('SearchInput', () => { const StatefulWrapper: React.FC<{onInput?: (input: string) => void}> = ({ onInput }) => { @@ -18,7 +19,7 @@ describe('SearchInput', () => { } it('should render', () => { - mount( {}} />) + mountAndSnapshot( {}} />) cy.get('input').should('exist') }) @@ -47,6 +48,8 @@ describe('SearchInput', () => { cy.get('input').type('some input') cy.get('[aria-label="Clear search"]').should('exist') + + cy.percySnapshot() }) it('should clear input on click', () => { diff --git a/npm/design-system/src/components/virtualizedTree/VirtualizedTree.spec.tsx b/npm/design-system/src/components/virtualizedTree/VirtualizedTree.spec.tsx index 01bdaa89b490..529e668fd7df 100644 --- a/npm/design-system/src/components/virtualizedTree/VirtualizedTree.spec.tsx +++ b/npm/design-system/src/components/virtualizedTree/VirtualizedTree.spec.tsx @@ -1,12 +1,14 @@ import * as React from 'react' -import { mount } from '@cypress/react' import { composeStories } from '@storybook/testing-react' -import * as stories from './VirtualizedTree.stories' +import { mountAndSnapshot } from 'util/testing' + +import * as stories from './VirtualizedTree.stories' const { VirtualizedTree } = composeStories(stories) +// TODO: Autogenerate from stories describe('', () => { - it('playground', () => { - mount() + it('VirtualizedTree', () => { + mountAndSnapshot() }) }) diff --git a/npm/design-system/src/core/button/Button.spec.tsx b/npm/design-system/src/core/button/Button.spec.tsx new file mode 100644 index 000000000000..68b7f7a0353f --- /dev/null +++ b/npm/design-system/src/core/button/Button.spec.tsx @@ -0,0 +1,28 @@ +import * as React from 'react' +import { composeStories } from '@storybook/testing-react' + +import { mountAndSnapshot } from 'util/testing' + +import * as stories from './Button.stories' +const { Button, IconButton } = composeStories(stories) + +// TODO: Autogenerate from stories +describe(' ))} {ifThen(!appState.isPaused, ( - + {appState.autoScrollingEnabled ? 'Disable' : 'Enable'} Auto-scrolling A

} className='cy-tooltip'>
diff --git a/packages/reporter/src/lib/events.ts b/packages/reporter/src/lib/events.ts index cf5d3ba9fa14..c6bca4489f80 100644 --- a/packages/reporter/src/lib/events.ts +++ b/packages/reporter/src/lib/events.ts @@ -152,6 +152,8 @@ const events: Events = { })) localBus.on('next', action('next', () => { + appState.resume() + statsStore.resume() runner.emit('runner:next') })) diff --git a/packages/reporter/src/lib/shortcuts.ts b/packages/reporter/src/lib/shortcuts.ts index 9bf2de2e99d9..43e3f9b6a27a 100644 --- a/packages/reporter/src/lib/shortcuts.ts +++ b/packages/reporter/src/lib/shortcuts.ts @@ -2,6 +2,7 @@ import dom from '@packages/driver/src/dom' import events from './events' import appState from './app-state' +import { action } from 'mobx' class Shortcuts { start () { @@ -23,6 +24,15 @@ class Shortcuts { case 's': !appState.isPaused && !appState.studioActive && events.emit('stop') break case 'f': events.emit('focus:tests') + break + case 'c': events.emit('resume') + break + case 'n': events.emit('next') + break + case 'a': action('set:scrolling', () => { + appState.setAutoScrolling(!appState.autoScrollingEnabled) + })() + break default: return } diff --git a/packages/runner-ct/cypress/component/screenshot.spec.tsx b/packages/runner-ct/cypress/component/screenshot.spec.tsx index cd4aa86ebbfd..eb5bb67d1f48 100644 --- a/packages/runner-ct/cypress/component/screenshot.spec.tsx +++ b/packages/runner-ct/cypress/component/screenshot.spec.tsx @@ -56,7 +56,7 @@ describe('screenshot', () => { // TODO: This will technically pass, but the screenshot is not correct. // AUT transform appears to be buggy for extreme viewports. - xit('screenshot with a really long viewport', () => { + it('screenshot with a really long viewport', () => { cy.viewport(200, 2000) mount(, { styles, @@ -64,4 +64,77 @@ describe('screenshot', () => { cy.screenshot('percy/component_testing_screenshot_long_viewport') }) + + const style = ` + html, body { + margin: 0; + padding: 0; + } + * { + box-sizing: content-box; + } + .wrapper { + width: 1500px; + height: 1000px; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; + grid-auto-rows: minmax(100px, auto); + } + .wrapper > div { + border: 5px solid orange; + border-radius: 10px; + padding: 10px; + margin: 10px; + background: rgba(233,171,88,.5); + } + .one { + grid-column: 1 / 3; + grid-row: 1; + } + .two { + grid-column: 2 / 4; + grid-row: 1 / 3; + } + .three { + grid-column: 1; + grid-row: 2 / 5; + } + .four { + grid-column: 3; + grid-row: 3; + } + .five { + grid-column: 2; + grid-row: 4; + } + .six { + grid-column: 3; + grid-row: 4; + } + ` + + ;[[1500, 850], [1500, 1000]].forEach((viewport) => { + it(`works with a large component with viewport ${viewport[0]} x ${viewport[1]}`, () => { + cy.viewport(viewport[0], viewport[1]) + + const Comp = () => { + return ( +
+
One
+
Two
+
Three
+
Four
+
Five
+
Six
+
+ ) + } + + mount(, { style }).then(() => { + cy.screenshot(`percy/large_component_hardcoded_size_viewport_${viewport[0]}_${viewport[1]}`, { capture: 'viewport' }) + cy.screenshot(`percy/large_component_hardcoded_size_fullPage_${viewport[0]}_${viewport[1]}`, { capture: 'fullPage' }) + }) + }) + }) }) diff --git a/packages/runner-ct/src/app/RunnerCt.scss b/packages/runner-ct/src/app/RunnerCt.scss index c8a1bb569fd6..7660b7667fb7 100644 --- a/packages/runner-ct/src/app/RunnerCt.scss +++ b/packages/runner-ct/src/app/RunnerCt.scss @@ -32,6 +32,11 @@ body, html { background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1IiBoZWlnaHQ9IjUiPgo8cmVjdCB3aWR0aD0iNSIgaGVpZ2h0PSI1IiBmaWxsPSIjZGJkYmRiIj48L3JlY3Q+CjxwYXRoIGQ9Ik0wIDVMNSAwWk02IDRMNCA2Wk0tMSAxTDEgLTFaIiBzdHJva2U9IiNjNGM0YzQiIHN0cm9rZS13aWR0aD0iMSI+PC9wYXRoPgo8L3N2Zz4="); } +.aut-iframe-screenshotting { + height: min(100vh, 100%) !important; + overflow: scroll !important; +} + // Prevent left-most Resizer from showing up when the pane is hidden. .isSpecsListClosed { > .Resizer { diff --git a/packages/runner-ct/src/app/useScreenshotHandler.tsx b/packages/runner-ct/src/app/useScreenshotHandler.tsx index 85df57d07a62..96a988ec2ab9 100644 --- a/packages/runner-ct/src/app/useScreenshotHandler.tsx +++ b/packages/runner-ct/src/app/useScreenshotHandler.tsx @@ -25,6 +25,10 @@ export function useScreenshotHandler ({ eventManager, state, splitPaneRef }: { splitPaneRef.current.splitPane.firstElementChild.classList.remove('d-none') splitPaneRef.current.splitPane.querySelector('[role="presentation"]').classList.remove('d-none') + + const iframe = document.querySelector('.aut-iframe') + + iframe.classList.remove('aut-iframe-screenshotting') } const hidePane = () => { @@ -34,6 +38,10 @@ export function useScreenshotHandler ({ eventManager, state, splitPaneRef }: { splitPaneRef.current.splitPane.firstElementChild.classList.add('d-none') splitPaneRef.current.splitPane.querySelector('[role="presentation"]').classList.add('d-none') + + const iframe = document.querySelector('.aut-iframe') + + iframe.classList.add('aut-iframe-screenshotting') } React.useEffect(() => { diff --git a/packages/runner-ct/src/iframe/iframes.jsx b/packages/runner-ct/src/iframe/iframes.jsx index 0335b0059153..6219020095f4 100644 --- a/packages/runner-ct/src/iframe/iframes.jsx +++ b/packages/runner-ct/src/iframe/iframes.jsx @@ -45,7 +45,7 @@ export default class Iframes extends Component { style={{ height: viewportHeight, width: viewportWidth, - transform: `scale(${scale})`, + transform: `scale(${screenshotting ? 1 : scale})`, }} /> diff --git a/packages/runner-ct/src/iframe/iframes.scss b/packages/runner-ct/src/iframe/iframes.scss index 658d80ddb313..76b8b66e92fb 100644 --- a/packages/runner-ct/src/iframe/iframes.scss +++ b/packages/runner-ct/src/iframe/iframes.scss @@ -16,4 +16,5 @@ .size-container { overflow: auto; box-shadow: shadow(m); + max-width: 100%; } diff --git a/packages/server/lib/config_options.ts b/packages/server/lib/config_options.ts index dde3162e4d60..dded69774f92 100644 --- a/packages/server/lib/config_options.ts +++ b/packages/server/lib/config_options.ts @@ -5,7 +5,7 @@ const v = require('./util/validation') // - cli/types/index.d.ts (including allowed config options on TestOptions) // - cypress.schema.json // -// Add options in alphabetical order +// Add options in alphabetical order for better readability export const options = [ { @@ -57,6 +57,10 @@ export const options = [ name: 'defaultCommandTimeout', defaultValue: 4000, validation: v.isNumber, + }, { + name: 'devServerPublicPathRoute', + defaultValue: '/__cypress/src', + isInternal: true, }, { name: 'downloadsFolder', defaultValue: 'cypress/downloads', @@ -223,9 +227,9 @@ export const options = [ defaultValue: '/__socket.io', isInternal: true, }, { - name: 'devServerPublicPathRoute', - defaultValue: '/__cypress/src', - isInternal: true, + name: 'scrollBehavior', + defaultValue: 'top', + validation: v.isOneOf('center', 'top', 'bottom', 'nearest', false), }, { name: 'socketIoCookie', defaultValue: '__socket.io', @@ -287,10 +291,6 @@ export const options = [ name: 'waitForAnimations', defaultValue: true, validation: v.isBoolean, - }, { - name: 'scrollBehavior', - defaultValue: 'top', - validation: v.isOneOf('center', 'top', 'bottom', 'nearest', false), }, { name: 'watchForFileChanges', defaultValue: true, diff --git a/packages/server/lib/plugins/index.js b/packages/server/lib/plugins/index.js index 54fafc0f8197..5573cdf35616 100644 --- a/packages/server/lib/plugins/index.js +++ b/packages/server/lib/plugins/index.js @@ -114,6 +114,12 @@ const init = (config, options) => { testingType: options.testingType, }) + // alphabetize config by keys + let orderedConfig = {} + + Object.keys(config).sort().forEach((key) => orderedConfig[key] = config[key]) + config = orderedConfig + ipc.send('load', config) ipc.on('loaded', (newCfg, registrations) => { diff --git a/packages/server/package.json b/packages/server/package.json index 27a09c0451a7..c8a1a43ede60 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -159,7 +159,7 @@ "eol": "0.9.1", "eventsource": "1.0.7", "express-session": "1.16.1", - "express-useragent": "1.0.12", + "express-useragent": "1.0.15", "http-mitm-proxy": "0.7.0", "https-proxy-agent": "3.0.1", "istanbul": "0.4.5", diff --git a/yarn.lock b/yarn.lock index 43ecfbe3f0ed..41763da5bdc6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18382,10 +18382,10 @@ express-session@1.16.1: safe-buffer "5.1.2" uid-safe "~2.1.5" -express-useragent@1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/express-useragent/-/express-useragent-1.0.12.tgz#5bae0109a925ec9b35417f31a4e8ad13f191253a" - integrity sha1-W64BCakl7Js1QX8xpOitE/GRJTo= +express-useragent@1.0.15: + version "1.0.15" + resolved "https://registry.yarnpkg.com/express-useragent/-/express-useragent-1.0.15.tgz#cefda5fa4904345d51d3368b117a8dd4124985d9" + integrity sha512-eq5xMiYCYwFPoekffMjvEIk+NWdlQY9Y38OsTyl13IvA728vKT+q/CSERYWzcw93HGBJcIqMIsZC5CZGARPVdg== express@4.17.1, express@^4.16.2, express@^4.16.3, express@^4.17.1: version "4.17.1" @@ -23877,10 +23877,10 @@ jpeg-js@^0.4.0: resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== -jquery.scrollto@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/jquery.scrollto/-/jquery.scrollto-2.1.2.tgz#e7580d9c7ac46ef5bb25319483f6f45713fd7c6c" - integrity sha1-51gNnHrEbvW7JTGUg/b0VxP9fGw= +jquery.scrollto@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/jquery.scrollto/-/jquery.scrollto-2.1.3.tgz#0ec03d5a64398f2e4dd6f3d316e091812ce4f29d" + integrity sha512-4M+t5R4QRnq1CSRU9gswZ7Z4zRfcoWmgD31HRrLYCCI4J7tLWeE0ZoT+0JpI7GcT0YKTqcfw+p8t2yYEKFfrHA== dependencies: jquery ">=1.8"