mirror of
https://github.com/cypress-io/cypress.git
synced 2026-02-22 15:11:00 -06:00
Merge branch 'develop' into chore/develop-into-release-13
This commit is contained in:
@@ -14,12 +14,14 @@ _Released 08/1/2023 (PENDING)_
|
||||
|
||||
## 12.15.0
|
||||
|
||||
_Released 06/20/2023 (PENDING)_
|
||||
_Released 06/20/2023_
|
||||
|
||||
**Features:**
|
||||
|
||||
- Added support for running Cypress tests with [Chrome's new `--headless=new` flag](https://developer.chrome.com/articles/new-headless/). Chrome versions 112 and above will now be run in the `headless` mode that matches the `headed` browser implementation. Addresses [#25972](https://github.com/cypress-io/cypress/issues/25972).
|
||||
- Cypress can now test pages with targeted `Content-Security-Policy` and `Content-Security-Policy-Report-Only` header directives by specifying the allow list via the [`experimentalCspAllowList`](https://docs.cypress.io/guides/references/configuration#Experimental-Csp-Allow-List) configuration option. Addresses [#1030](https://github.com/cypress-io/cypress/issues/1030). Addressed in [#26483](https://github.com/cypress-io/cypress/pull/26483)
|
||||
- The [`videoCompression`](https://docs.cypress.io/guides/references/configuration#Videos) configuration option now accepts both a boolean or a Constant Rate Factor (CRF) number between `1` and `51`. The `videoCompression` default value is still `32` CRF and when `videoCompression` is set to `true` the default of `32` CRF will be used. Addresses [#26658](https://github.com/cypress-io/cypress/issues/26658).
|
||||
- The Cypress Cloud data shown on the [Specs](https://docs.cypress.io/guides/core-concepts/cypress-app#Specs) page and [Runs](https://docs.cypress.io/guides/core-concepts/cypress-app#Runs) page will now reflect Cloud Runs that match the current Git tree if Git is being used. Addresses [#26693](https://github.com/cypress-io/cypress/issues/26693).
|
||||
|
||||
**Bugfixes:**
|
||||
|
||||
|
||||
4
cli/types/cypress-eventemitter.d.ts
vendored
4
cli/types/cypress-eventemitter.d.ts
vendored
@@ -3,8 +3,8 @@ type EventEmitter2 = import("eventemitter2").EventEmitter2
|
||||
|
||||
interface CyEventEmitter extends Omit<EventEmitter2, 'waitFor'> {
|
||||
proxyTo: (cy: Cypress.cy) => null
|
||||
emitMap: (eventName: string, args: any[]) => Array<(...args: any[]) => any>
|
||||
emitThen: (eventName: string, args: any[]) => Bluebird.BluebirdStatic
|
||||
emitMap: (eventName: string, ...args: any[]) => Array<(...args: any[]) => any>
|
||||
emitThen: (eventName: string, ...args: any[]) => Bluebird.BluebirdStatic
|
||||
}
|
||||
|
||||
// Copied from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/events.d.ts
|
||||
|
||||
18
cli/types/cypress.d.ts
vendored
18
cli/types/cypress.d.ts
vendored
@@ -2672,6 +2672,8 @@ declare namespace Cypress {
|
||||
force: boolean
|
||||
}
|
||||
|
||||
type experimentalCspAllowedDirectives = 'default-src' | 'child-src' | 'frame-src' | 'script-src' | 'script-src-elem' | 'form-action'
|
||||
|
||||
type scrollBehaviorOptions = false | 'center' | 'top' | 'bottom' | 'nearest'
|
||||
|
||||
/**
|
||||
@@ -3041,6 +3043,19 @@ declare namespace Cypress {
|
||||
* @default 'top'
|
||||
*/
|
||||
scrollBehavior: scrollBehaviorOptions
|
||||
/**
|
||||
* Indicates whether Cypress should allow CSP header directives from the application under test.
|
||||
* - When this option is set to `false`, Cypress will strip the entire CSP header.
|
||||
* - When this option is set to `true`, Cypress will only to strip directives that would interfere
|
||||
* with or inhibit Cypress functionality.
|
||||
* - When this option to an array of allowable directives (`[ 'default-src', ... ]`), the directives
|
||||
* specified will remain in the response headers.
|
||||
*
|
||||
* Please see the documentation for more information.
|
||||
* @see https://on.cypress.io/experiments#Experimental-CSP-Allow-List
|
||||
* @default false
|
||||
*/
|
||||
experimentalCspAllowList: boolean | experimentalCspAllowedDirectives[],
|
||||
/**
|
||||
* Allows listening to the `before:run`, `after:run`, `before:spec`, and `after:spec` events in the plugins file during interactive mode.
|
||||
* @default false
|
||||
@@ -3052,7 +3067,7 @@ declare namespace Cypress {
|
||||
* Please see https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity.
|
||||
* This option has no impact on experimentalSourceRewriting and is only used with the
|
||||
* non-experimental source rewriter.
|
||||
* @see https://on.cypress.io/configuration#experimentalModifyObstructiveThirdPartyCode
|
||||
* @see https://on.cypress.io/experiments#Configuration
|
||||
*/
|
||||
experimentalModifyObstructiveThirdPartyCode: boolean
|
||||
/**
|
||||
@@ -3062,6 +3077,7 @@ declare namespace Cypress {
|
||||
* navigations, and will require the use of cy.origin(). This option takes an array of
|
||||
* strings/string globs.
|
||||
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/domain
|
||||
* @see https://on.cypress.io/experiments#Experimental-Skip-Domain-Injection
|
||||
* @default null
|
||||
*/
|
||||
experimentalSkipDomainInjection: string[] | null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cypress",
|
||||
"version": "12.14.0",
|
||||
"version": "12.15.0",
|
||||
"description": "Cypress is a next generation front end testing tool built for the modern web",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
3
packages/app/.gitignore
vendored
3
packages/app/.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
cypress/videos/*
|
||||
cypress/screenshots/*
|
||||
cypress/downloads/*
|
||||
|
||||
components.d.ts
|
||||
components.d.ts
|
||||
|
||||
@@ -11,6 +11,7 @@ export default defineConfig({
|
||||
reporterOptions: {
|
||||
configFile: '../../mocha-reporter-config.json',
|
||||
},
|
||||
experimentalCspAllowList: false,
|
||||
experimentalInteractiveRunEvents: true,
|
||||
component: {
|
||||
experimentalSingleTabRunMode: true,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { OpenFileInIdeQuery } from '../../src/generated/graphql-test'
|
||||
import RelevantRunsDataSource_RunsByCommitShas from '../fixtures/gql-RelevantRunsDataSource_RunsByCommitShas.json'
|
||||
import DebugDataPassing from '../fixtures/debug-Passing/gql-Debug.json'
|
||||
import DebugDataFailing from '../fixtures/debug-Failing/gql-Debug.json'
|
||||
|
||||
Cypress.on('window:before:load', (win) => {
|
||||
win.__CYPRESS_GQL_NO_SOCKET__ = 'true'
|
||||
@@ -16,45 +18,26 @@ describe('App - Debug Page', () => {
|
||||
cy.startAppServer('component')
|
||||
|
||||
cy.loginUser()
|
||||
cy.withCtx((ctx) => {
|
||||
cy.withCtx((ctx, o) => {
|
||||
ctx.git?.__setGitHashesForTesting(['commit1', 'commit2'])
|
||||
o.sinon.stub(ctx.lifecycleManager.git!, 'currentBranch').value('fakeBranch')
|
||||
})
|
||||
})
|
||||
|
||||
it('all tests passed', () => {
|
||||
cy.remoteGraphQLIntercept((obj, _testState, options) => {
|
||||
if (obj.operationName === 'RelevantRunsDataSource_RunsByCommitShas') {
|
||||
obj.result.data = options.RelevantRunsDataSource_RunsByCommitShas.data
|
||||
}
|
||||
|
||||
if (obj.operationName === 'Debug_currentProject_cloudProject_cloudProjectBySlug') {
|
||||
if (obj.result.data) {
|
||||
obj.result.data.cloudProjectBySlug.runByNumber = options.DebugDataPassing.data.currentProject.cloudProject.runByNumber
|
||||
}
|
||||
}
|
||||
|
||||
return obj.result
|
||||
}, { RelevantRunsDataSource_RunsByCommitShas })
|
||||
})
|
||||
|
||||
it('all tests passed', () => {
|
||||
// This mocks all the responses so we can get deterministic
|
||||
// results to test the debug page.
|
||||
cy.intercept('query-Debug', {
|
||||
fixture: 'debug-Passing/gql-Debug.json',
|
||||
})
|
||||
|
||||
cy.intercept('query-CloudViewerAndProject_RequiredData', {
|
||||
fixture: 'debug-Passing/gql-CloudViewerAndProject_RequiredData.json',
|
||||
})
|
||||
|
||||
cy.intercept('query-MainAppQuery', {
|
||||
fixture: 'debug-Passing/gql-MainAppQuery.json',
|
||||
})
|
||||
|
||||
cy.intercept('query-SideBarNavigationContainer', {
|
||||
fixture: 'debug-Passing/gql-SideBarNavigationContainer',
|
||||
})
|
||||
|
||||
cy.intercept('query-HeaderBar_HeaderBarQuery', {
|
||||
fixture: 'debug-Passing/gql-HeaderBar_HeaderBarQuery',
|
||||
})
|
||||
|
||||
cy.intercept('query-SpecsPageContainer', {
|
||||
fixture: 'debug-Passing/gql-SpecsPageContainer',
|
||||
})
|
||||
}, { RelevantRunsDataSource_RunsByCommitShas, DebugDataPassing })
|
||||
|
||||
cy.visitApp()
|
||||
|
||||
@@ -84,32 +67,24 @@ describe('App - Debug Page', () => {
|
||||
cy.findByTestId('debug-passed').contains('All your tests passed.')
|
||||
cy.findByLabelText('Relevant run passed').should('be.visible').contains('0')
|
||||
cy.findByTestId('run-failures').should('not.exist')
|
||||
|
||||
cy.get('[data-cy="debug-badge"]').should('be.visible').contains('0')
|
||||
})
|
||||
|
||||
it('shows information about a failed spec', () => {
|
||||
cy.intercept('query-Debug', {
|
||||
fixture: 'debug-Failing/gql-Debug.json',
|
||||
})
|
||||
cy.remoteGraphQLIntercept((obj, _testState, options) => {
|
||||
if (obj.operationName === 'RelevantRunsDataSource_RunsByCommitShas') {
|
||||
obj.result.data = options.RelevantRunsDataSource_RunsByCommitShas.data
|
||||
}
|
||||
|
||||
cy.intercept('query-CloudViewerAndProject_RequiredData', {
|
||||
fixture: 'debug-Failing/gql-CloudViewerAndProject_RequiredData.json',
|
||||
})
|
||||
if (obj.operationName === 'Debug_currentProject_cloudProject_cloudProjectBySlug') {
|
||||
if (obj.result.data) {
|
||||
obj.result.data.cloudProjectBySlug.runByNumber = options.DebugDataFailing.data.currentProject.cloudProject.runByNumber
|
||||
}
|
||||
}
|
||||
|
||||
cy.intercept('query-MainAppQuery', {
|
||||
fixture: 'debug-Failing/gql-MainAppQuery.json',
|
||||
})
|
||||
|
||||
cy.intercept('query-SideBarNavigationContainer', {
|
||||
fixture: 'debug-Failing/gql-SideBarNavigationContainer',
|
||||
})
|
||||
|
||||
cy.intercept('query-HeaderBar_HeaderBarQuery', {
|
||||
fixture: 'debug-Failing/gql-HeaderBar_HeaderBarQuery',
|
||||
})
|
||||
|
||||
cy.intercept('query-SpecsPageContainer', {
|
||||
fixture: 'debug-Failing/gql-SpecsPageContainer',
|
||||
})
|
||||
return obj.result
|
||||
}, { RelevantRunsDataSource_RunsByCommitShas, DebugDataFailing })
|
||||
|
||||
cy.intercept('query-OpenFileInIDE', (req) => {
|
||||
req.on('response', (res) => {
|
||||
@@ -152,7 +127,7 @@ describe('App - Debug Page', () => {
|
||||
})
|
||||
|
||||
cy.findByTestId('spec-contents').within(() => {
|
||||
cy.contains('src/components/InfoPanel/InfoPanel.cy.ts')
|
||||
cy.contains('src/NewComponent.spec.jsx')
|
||||
cy.findByTestId('metaData-Results-spec-duration').contains('00:04')
|
||||
cy.findByTestId('metaData-Results-operating-system').contains('Linux Ubuntu')
|
||||
cy.findByTestId('metaData-Results-browser').contains('Electron 106')
|
||||
@@ -161,12 +136,95 @@ describe('App - Debug Page', () => {
|
||||
|
||||
cy.findByTestId('test-row').contains('InfoPanel')
|
||||
cy.findByTestId('test-row').contains('renders')
|
||||
cy.findByTestId('run-failures').should('exist').should('have.attr', 'href', '#/specs/runner?file=src/components/InfoPanel/InfoPanel.cy.ts&mode=debug')
|
||||
cy.findByTestId('run-failures').should('exist').should('have.attr', 'href', '#/specs/runner?file=src/NewComponent.spec.jsx&mode=debug')
|
||||
|
||||
cy.findByLabelText('Open in IDE').click()
|
||||
cy.wait('@openFileInIDE')
|
||||
cy.withCtx((ctx) => {
|
||||
expect(ctx.actions.file.openFile).to.have.been.calledWith('src/components/InfoPanel/InfoPanel.cy.ts', 1, 1)
|
||||
expect(ctx.actions.file.openFile).to.have.been.calledWith('src/NewComponent.spec.jsx', 1, 1)
|
||||
})
|
||||
})
|
||||
|
||||
it('shows running and updating build', () => {
|
||||
cy.remoteGraphQLIntercept((obj, _testState, options) => {
|
||||
if (obj.operationName === 'RelevantRunsDataSource_RunsByCommitShas') {
|
||||
obj.result.data = options.RelevantRunsDataSource_RunsByCommitShas.data
|
||||
}
|
||||
|
||||
const originalRun = options.DebugDataFailing.data.currentProject.cloudProject.runByNumber
|
||||
|
||||
if (options.testRun === undefined) {
|
||||
options.testRun = JSON.parse(JSON.stringify(originalRun))
|
||||
}
|
||||
|
||||
const run = options.testRun
|
||||
|
||||
run.totalInstanceCount = 5
|
||||
if (run.completedInstanceCount === undefined) {
|
||||
run.completedInstanceCount = 0
|
||||
run.createdAt = (new Date()).toISOString()
|
||||
}
|
||||
|
||||
if (run.totalInstanceCount === run.completedInstanceCount) {
|
||||
run.status = 'FAILED'
|
||||
} else {
|
||||
run.status = 'RUNNING'
|
||||
}
|
||||
|
||||
if (run.completedInstanceCount < 3) {
|
||||
run.testsForReview = []
|
||||
} else {
|
||||
run.testsForReview = originalRun.testsForReview
|
||||
}
|
||||
|
||||
run.totalFailed = run.testsForReview.length
|
||||
run.totalPassed = run.completedInstanceCount - run.totalFailed
|
||||
|
||||
if (obj.operationName === 'Debug_currentProject_cloudProject_cloudProjectBySlug') {
|
||||
if (obj.result.data) {
|
||||
obj.result.data.cloudProjectBySlug.runByNumber = run
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.operationName === 'RelevantRunSpecsDataSource_Specs' && obj.result.data) {
|
||||
//NOTE Figure out how to manually trigger polling instead of adjusting polling intervals
|
||||
obj.result.data.pollingIntervals = {
|
||||
__typename: 'CloudPollingIntervals',
|
||||
runByNumber: 1, //Increase polling interval for debugging test
|
||||
}
|
||||
|
||||
if (run.totalInstanceCount === run.completedInstanceCount) {
|
||||
obj.result.data.pollingIntervals.runByNumber = 100
|
||||
} else {
|
||||
run.completedInstanceCount = run.completedInstanceCount !== undefined ? ++run.completedInstanceCount : 0
|
||||
}
|
||||
|
||||
obj.result.data.cloudNodesByIds = [
|
||||
run,
|
||||
]
|
||||
}
|
||||
|
||||
return obj.result
|
||||
}, { RelevantRunsDataSource_RunsByCommitShas, DebugDataFailing })
|
||||
|
||||
cy.visitApp()
|
||||
|
||||
cy.findByTestId('sidebar-link-debug-page').click()
|
||||
cy.findByTestId('debug-container').should('be.visible')
|
||||
|
||||
cy.findByTestId('header-top').contains('chore: testing cypress')
|
||||
|
||||
cy.findByTestId('debug-testing-progress').as('progress')
|
||||
|
||||
cy.get('@progress').contains('Testing in progress...')
|
||||
cy.get('[data-cy="debug-badge"]').contains('0').should('be.visible')
|
||||
cy.get('@progress').contains('1 of 5 specs completed')
|
||||
cy.get('@progress').contains('2 of 5 specs completed')
|
||||
cy.get('@progress').contains('3 of 5 specs completed')
|
||||
cy.get('[data-cy="debug-badge"]').contains('1').should('be.visible')
|
||||
|
||||
cy.findByTestId('spec-contents').within(() => {
|
||||
cy.contains('src/NewComponent.spec.jsx')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -343,18 +343,6 @@ export const generateCtErrorTests = (server: 'Webpack' | 'Vite', configFile: str
|
||||
})
|
||||
})
|
||||
|
||||
it('cy.readFile', () => {
|
||||
const verify = loadErrorSpec({
|
||||
filePath: 'errors/readfile.cy.js',
|
||||
failCount: 1,
|
||||
}, configFile)
|
||||
|
||||
verify('existence failure', {
|
||||
column: [8, 9],
|
||||
message: 'failed because the file does not exist',
|
||||
})
|
||||
})
|
||||
|
||||
it('validation errors', () => {
|
||||
const verify = loadErrorSpec({
|
||||
filePath: 'errors/validation.cy.js',
|
||||
|
||||
@@ -321,18 +321,6 @@ describe('errors ui', {
|
||||
})
|
||||
})
|
||||
|
||||
it('cy.readFile', () => {
|
||||
const verify = loadErrorSpec({
|
||||
filePath: 'errors/readfile.cy.js',
|
||||
failCount: 1,
|
||||
})
|
||||
|
||||
verify('existence failure', {
|
||||
column: 8,
|
||||
message: 'failed because the file does not exist',
|
||||
})
|
||||
})
|
||||
|
||||
it('validation errors', () => {
|
||||
const verify = loadErrorSpec({
|
||||
filePath: 'errors/validation.cy.js',
|
||||
|
||||
@@ -2,10 +2,6 @@ import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json'
|
||||
import type { SinonStub } from 'sinon'
|
||||
|
||||
function moveToRunsPage (): void {
|
||||
cy.withCtx((ctx, o) => {
|
||||
o.sinon.stub(ctx.lifecycleManager.git!, 'currentBranch').value('fakeBranch')
|
||||
})
|
||||
|
||||
cy.findByTestId('sidebar-link-runs-page').click()
|
||||
cy.findByTestId('app-header-bar').findByText('Runs').should('be.visible')
|
||||
cy.findByTestId('runs-container').should('be.visible')
|
||||
@@ -38,15 +34,12 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
|
||||
cy.scaffoldProject('component-tests')
|
||||
cy.openProject('component-tests')
|
||||
cy.startAppServer('component')
|
||||
cy.withCtx((ctx, o) => {
|
||||
o.sinon.stub(ctx.lifecycleManager.git!, 'currentBranch').value('fakeBranch')
|
||||
})
|
||||
})
|
||||
|
||||
it('resolves the runs page', () => {
|
||||
cy.loginUser()
|
||||
cy.visitApp()
|
||||
cy.get('[href="#/runs"]', { timeout: 1000 }).click()
|
||||
moveToRunsPage()
|
||||
cy.get('[data-cy="runs"]')
|
||||
cy.get('[data-cy="app-header-bar"]').findByText('Runs').should('be.visible')
|
||||
})
|
||||
@@ -322,7 +315,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
|
||||
cy.withCtx(async (ctx, o) => {
|
||||
o.sinon.spy(ctx.cloud, 'executeRemoteGraphQL')
|
||||
|
||||
o.sinon.stub(ctx.lifecycleManager.git!, 'currentBranch').value('fakeBranch')
|
||||
//o.sinon.stub(ctx.lifecycleManager.git!, 'currentBranch').value('fakeBranch')
|
||||
const config = await ctx.project.getConfig()
|
||||
|
||||
expect(config.projectId).to.not.equal('newProjectId')
|
||||
@@ -648,98 +641,156 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
|
||||
})
|
||||
|
||||
context('Runs - Runs List', () => {
|
||||
beforeEach(() => {
|
||||
cy.scaffoldProject('component-tests')
|
||||
cy.openProject('component-tests')
|
||||
cy.startAppServer('component')
|
||||
})
|
||||
|
||||
it('displays a list of recorded runs if a run has been recorded', () => {
|
||||
cy.loginUser()
|
||||
cy.visitApp()
|
||||
moveToRunsPage()
|
||||
cy.get('[data-cy="runs"]')
|
||||
})
|
||||
|
||||
it('displays each run with correct information', () => {
|
||||
cy.loginUser()
|
||||
cy.visitApp()
|
||||
moveToRunsPage()
|
||||
|
||||
cy.get('[href^="http://dummy.cypress.io/runs/0"]').first().within(() => {
|
||||
cy.findByText('fix: make gql work CANCELLED')
|
||||
cy.get('[data-cy="run-card-icon-CANCELLED"]')
|
||||
context('no Git data', () => {
|
||||
beforeEach(() => {
|
||||
cy.scaffoldProject('component-tests')
|
||||
cy.openProject('component-tests')
|
||||
cy.startAppServer('component')
|
||||
})
|
||||
|
||||
cy.get('[href^="http://dummy.cypress.io/runs/1"]').first().within(() => {
|
||||
cy.findByText('fix: make gql work ERRORED')
|
||||
cy.get('[data-cy="run-card-icon-ERRORED"]')
|
||||
it('displays a list of recorded runs if a run has been recorded', () => {
|
||||
cy.loginUser()
|
||||
cy.visitApp()
|
||||
moveToRunsPage()
|
||||
cy.get('[data-cy="runs"]')
|
||||
})
|
||||
|
||||
cy.get('[href^="http://dummy.cypress.io/runs/2"]').first().within(() => {
|
||||
cy.findByText('fix: make gql work FAILED')
|
||||
cy.get('[data-cy="run-card-icon-FAILED"]')
|
||||
it('displays each run with correct information', () => {
|
||||
cy.loginUser()
|
||||
cy.visitApp()
|
||||
moveToRunsPage()
|
||||
|
||||
cy.get('[href^="http://dummy.cypress.io/runs/0"]').first().within(() => {
|
||||
cy.findByText('fix: make gql work CANCELLED')
|
||||
cy.get('[data-cy="run-card-icon-CANCELLED"]')
|
||||
})
|
||||
|
||||
cy.get('[href^="http://dummy.cypress.io/runs/1"]').first().within(() => {
|
||||
cy.findByText('fix: make gql work ERRORED')
|
||||
cy.get('[data-cy="run-card-icon-ERRORED"]')
|
||||
})
|
||||
|
||||
cy.get('[href^="http://dummy.cypress.io/runs/2"]').first().within(() => {
|
||||
cy.findByText('fix: make gql work FAILED')
|
||||
cy.get('[data-cy="run-card-icon-FAILED"]')
|
||||
})
|
||||
|
||||
cy.get('[href^="http://dummy.cypress.io/runs/0"]').first().as('firstRun')
|
||||
|
||||
cy.get('@firstRun').within(() => {
|
||||
cy.get('[data-cy="run-card-author"]').contains('John Appleseed')
|
||||
cy.get('[data-cy="run-card-avatar"]')
|
||||
cy.get('[data-cy="run-card-branch"]').contains('main')
|
||||
cy.get('[data-cy="run-card-created-at"]').contains('an hour ago')
|
||||
cy.get('[data-cy="run-card-duration"]').contains('01:00')
|
||||
|
||||
cy.contains('span', 'skipped')
|
||||
cy.get('span').contains('pending')
|
||||
cy.get('span').contains('passed')
|
||||
cy.get('span').contains('failed')
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('[href^="http://dummy.cypress.io/runs/0"]').first().as('firstRun')
|
||||
it('opens the run page if a run is clicked', () => {
|
||||
cy.loginUser()
|
||||
cy.visitApp()
|
||||
|
||||
cy.get('@firstRun').within(() => {
|
||||
cy.get('[data-cy="run-card-author"]').contains('John Appleseed')
|
||||
cy.get('[data-cy="run-card-avatar"]')
|
||||
cy.get('[data-cy="run-card-branch"]').contains('main')
|
||||
cy.get('[data-cy="run-card-created-at"]').contains('an hour ago')
|
||||
cy.get('[data-cy="run-card-duration"]').contains('01:00')
|
||||
moveToRunsPage()
|
||||
cy.get('[data-cy^="runCard-"]').first().click()
|
||||
|
||||
cy.contains('span', 'skipped')
|
||||
cy.get('span').contains('pending')
|
||||
cy.get('span').contains('passed')
|
||||
cy.get('span').contains('failed')
|
||||
cy.withCtx((ctx) => {
|
||||
expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.contain('http://dummy.cypress.io/runs/0')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('opens the run page if a run is clicked', () => {
|
||||
cy.loginUser()
|
||||
cy.visitApp()
|
||||
it('shows connection failed error if no cloudProject', () => {
|
||||
let cloudData: any
|
||||
|
||||
moveToRunsPage()
|
||||
cy.get('[data-cy^="runCard-"]').first().click()
|
||||
cy.loginUser()
|
||||
cy.remoteGraphQLIntercept((obj) => {
|
||||
if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) {
|
||||
cloudData = obj.result
|
||||
obj.result = {}
|
||||
|
||||
cy.withCtx((ctx) => {
|
||||
expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.contain('http://dummy.cypress.io/runs/0')
|
||||
})
|
||||
})
|
||||
|
||||
it('shows connection failed error if no cloudProject', () => {
|
||||
let cloudData: any
|
||||
|
||||
cy.loginUser()
|
||||
cy.remoteGraphQLIntercept((obj) => {
|
||||
if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) {
|
||||
cloudData = obj.result
|
||||
obj.result = {}
|
||||
return obj.result
|
||||
}
|
||||
|
||||
return obj.result
|
||||
}
|
||||
})
|
||||
|
||||
return obj.result
|
||||
cy.visitApp()
|
||||
|
||||
moveToRunsPage()
|
||||
|
||||
cy.contains('h2', 'Cannot connect to Cypress Cloud')
|
||||
// cy.percySnapshot() // TODO: restore when Percy CSS is fixed. See https://github.com/cypress-io/cypress/issues/23435
|
||||
|
||||
cy.remoteGraphQLIntercept((obj) => {
|
||||
if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) {
|
||||
return cloudData
|
||||
}
|
||||
|
||||
return obj.result
|
||||
})
|
||||
|
||||
cy.contains('button', 'Try again').click().should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
context('has Git data', () => {
|
||||
beforeEach(() => {
|
||||
cy.scaffoldProject('component-tests')
|
||||
.then((projectPath) => {
|
||||
cy.task('initGitRepoForTestProject', projectPath)
|
||||
cy.openProject('component-tests')
|
||||
cy.startAppServer('component')
|
||||
})
|
||||
})
|
||||
|
||||
cy.visitApp()
|
||||
|
||||
moveToRunsPage()
|
||||
|
||||
cy.contains('h2', 'Cannot connect to Cypress Cloud')
|
||||
// cy.percySnapshot() // TODO: restore when Percy CSS is fixed. See https://github.com/cypress-io/cypress/issues/23435
|
||||
|
||||
cy.remoteGraphQLIntercept((obj) => {
|
||||
if (obj.operationName?.includes('cloudProject_cloudProjectBySlug')) {
|
||||
return cloudData
|
||||
}
|
||||
|
||||
return obj.result
|
||||
it('displays a list of recorded runs if a run has been recorded', () => {
|
||||
cy.loginUser()
|
||||
cy.visitApp()
|
||||
moveToRunsPage()
|
||||
cy.get('[data-cy="runs"]')
|
||||
})
|
||||
|
||||
cy.contains('button', 'Try again').click().should('not.exist')
|
||||
it('displays each run with correct information', () => {
|
||||
cy.loginUser()
|
||||
cy.visitApp()
|
||||
moveToRunsPage()
|
||||
|
||||
cy.get('[href^="http://dummy.cypress.io/runs/0"]').first().within(() => {
|
||||
cy.findByText('fix: using Git data CANCELLED')
|
||||
cy.get('[data-cy="run-card-icon-CANCELLED"]')
|
||||
})
|
||||
|
||||
cy.get('[href^="http://dummy.cypress.io/runs/0"]').first().as('firstRun')
|
||||
|
||||
cy.get('@firstRun').within(() => {
|
||||
cy.get('[data-cy="run-card-author"]').contains('John Appleseed')
|
||||
cy.get('[data-cy="run-card-avatar"]')
|
||||
cy.get('[data-cy="run-card-branch"]').contains('main')
|
||||
cy.get('[data-cy="run-card-created-at"]').contains('an hour ago')
|
||||
cy.get('[data-cy="run-card-duration"]').contains('01:00')
|
||||
|
||||
cy.contains('span', 'skipped')
|
||||
cy.get('span').contains('pending')
|
||||
cy.get('span').contains('passed')
|
||||
cy.get('span').contains('failed')
|
||||
})
|
||||
})
|
||||
|
||||
it('opens the run page if a run is clicked', () => {
|
||||
cy.loginUser()
|
||||
cy.visitApp()
|
||||
|
||||
moveToRunsPage()
|
||||
cy.get('[data-cy^="runCard-"]').first().click()
|
||||
|
||||
cy.withCtx((ctx) => {
|
||||
expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.contain('http://dummy.cypress.io/runs/0')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -765,10 +816,6 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
|
||||
})
|
||||
|
||||
it('should remove the alert warning if the app reconnects to the internet', () => {
|
||||
cy.withCtx((ctx, o) => {
|
||||
o.sinon.stub(ctx.lifecycleManager.git!, 'currentBranch').value('fakeBranch')
|
||||
})
|
||||
|
||||
cy.loginUser()
|
||||
cy.visitApp()
|
||||
cy.wait(1000)
|
||||
@@ -783,7 +830,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
|
||||
|
||||
cy.goOnline()
|
||||
|
||||
cy.get('[data-cy=warning-alert]').should('not.exist')
|
||||
cy.contains('You have no internet connection').should('not.exist')
|
||||
})
|
||||
|
||||
it('shows correct message on create org modal', () => {
|
||||
@@ -861,39 +908,41 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
|
||||
const RUNNING_COUNT = 3
|
||||
|
||||
describe('refetching', () => {
|
||||
let obj: {toCall?: Function} = {}
|
||||
|
||||
beforeEach(() => {
|
||||
cy.scaffoldProject('component-tests')
|
||||
cy.openProject('component-tests')
|
||||
cy.startAppServer('component')
|
||||
cy.loginUser()
|
||||
cy.remoteGraphQLIntercept((obj) => {
|
||||
if (obj.result.data?.cloudProjectBySlug?.runs?.nodes.length) {
|
||||
obj.result.data.cloudProjectBySlug.runs.nodes.map((run) => {
|
||||
run.status = 'RUNNING'
|
||||
})
|
||||
if (obj.operationName === 'Runs_currentProject_cloudProject_cloudProjectBySlug') {
|
||||
if (obj.result.data?.cloudProjectBySlug?.runs?.nodes.length) {
|
||||
obj.result.data.cloudProjectBySlug.runs.nodes.map((run) => {
|
||||
run.status = 'RUNNING'
|
||||
})
|
||||
|
||||
obj.result.data.cloudProjectBySlug.runs.nodes = obj.result.data.cloudProjectBySlug.runs.nodes.slice(0, 3)
|
||||
obj.result.data.cloudProjectBySlug.runs.nodes = obj.result.data.cloudProjectBySlug.runs.nodes.slice(0, 3)
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.operationName === 'RelevantRunSpecsDataSource_Specs') {
|
||||
if (obj.result.data?.cloudNodesByIds) {
|
||||
obj.result.data?.cloudNodesByIds.map((node) => {
|
||||
node.status = 'RUNNING'
|
||||
})
|
||||
}
|
||||
|
||||
if (obj.result.data) {
|
||||
obj.result.data.pollingIntervals = {
|
||||
__typename: 'CloudPollingIntervals',
|
||||
runByNumber: 0.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj.result
|
||||
})
|
||||
|
||||
cy.visitApp('/runs', {
|
||||
onBeforeLoad (win) {
|
||||
const setTimeout = win.setTimeout
|
||||
|
||||
// @ts-expect-error
|
||||
win.setTimeout = function (fn: () => void, time: number) {
|
||||
if (fn.name === 'fetchNewerRuns') {
|
||||
obj.toCall = fn
|
||||
} else {
|
||||
setTimeout(fn, time)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
cy.visitApp('/runs')
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/24575
|
||||
@@ -921,54 +970,13 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
|
||||
})
|
||||
|
||||
function completeNext (passed) {
|
||||
cy.wrap(obj).invoke('toCall').then(() => {
|
||||
cy.get('[data-cy="run-card-icon-PASSED"]').should('have.length', passed).should('be.visible')
|
||||
if (passed < RUNNING_COUNT) {
|
||||
completeNext(passed + 1)
|
||||
}
|
||||
})
|
||||
cy.get('[data-cy="run-card-icon-PASSED"]').should('have.length', passed).should('be.visible')
|
||||
if (passed < RUNNING_COUNT) {
|
||||
completeNext(passed + 1)
|
||||
}
|
||||
}
|
||||
|
||||
completeNext(1)
|
||||
})
|
||||
|
||||
// TODO: unskip https://github.com/cypress-io/cypress/issues/24575
|
||||
it.skip('should fetch newer runs and maintain them when navigating', () => {
|
||||
cy.get('[data-cy="run-card-icon-RUNNING"]').should('have.length', RUNNING_COUNT).should('be.visible')
|
||||
|
||||
cy.remoteGraphQLIntercept(async (obj) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
|
||||
if (obj.result.data?.cloudNodesByIds) {
|
||||
obj.result.data?.cloudNodesByIds.map((node) => {
|
||||
node.status = 'PASSED'
|
||||
node.totalPassed = 100
|
||||
})
|
||||
}
|
||||
|
||||
return obj.result
|
||||
})
|
||||
|
||||
cy.get('[data-cy="run-card-icon-RUNNING"]').should('have.length', 3).should('be.visible')
|
||||
cy.wrap(obj).invoke('toCall')
|
||||
|
||||
cy.get('[data-cy="run-card-icon-PASSED"]')
|
||||
.should('have.length', 3)
|
||||
.should('be.visible')
|
||||
.first().within(() => {
|
||||
cy.get('[data-cy="runResults-passed-count"]').should('contain', 100)
|
||||
})
|
||||
|
||||
cy.get('[data-cy="run-card-icon-RUNNING"]').should('have.length', 2).should('be.visible')
|
||||
|
||||
// If we navigate away & back, we should see the same runs
|
||||
cy.findByTestId('sidebar-link-settings-page').click()
|
||||
cy.remoteGraphQLIntercept((obj) => obj.result)
|
||||
|
||||
moveToRunsPage()
|
||||
|
||||
cy.get('[data-cy="run-card-icon-PASSED"]').should('have.length', 3).should('be.visible')
|
||||
cy.get('[data-cy="run-card-icon-RUNNING"]').should('have.length', 2).should('be.visible')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,6 +10,8 @@ describe('App: Spec List - Flaky Indicator', () => {
|
||||
o.sinon.stub(ctx.project, 'projectId').resolves('abc123')
|
||||
// Must have an active Git branch in order to fetch flaky data (see @include($hasBranch) restriction)
|
||||
o.sinon.stub(ctx.lifecycleManager.git!, 'currentBranch').value('fakeBranch')
|
||||
|
||||
ctx.git?.__setGitHashesForTesting(['commit1', 'commit2'])
|
||||
})
|
||||
|
||||
cy.remoteGraphQLIntercept(async (obj) => {
|
||||
@@ -20,13 +22,10 @@ describe('App: Spec List - Flaky Indicator', () => {
|
||||
__typename: 'CloudProjectSpec',
|
||||
id: `id${obj.variables.specPath}`,
|
||||
retrievedAt: new Date().toISOString(),
|
||||
averageDuration: null,
|
||||
specRuns: {
|
||||
__typename: 'CloudSpecRunConnection',
|
||||
nodes: [],
|
||||
},
|
||||
isConsideredFlaky: true,
|
||||
flakyStatus: {
|
||||
averageDurationForRunIds: null,
|
||||
specRunsForRunIds: [],
|
||||
isConsideredFlakyForRunIds: true,
|
||||
flakyStatusForRunIds: {
|
||||
__typename: 'CloudProjectSpecFlakyStatus',
|
||||
severity: 'LOW',
|
||||
flakyRuns: 2,
|
||||
@@ -38,6 +37,41 @@ describe('App: Spec List - Flaky Indicator', () => {
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.operationName === 'RelevantRunsDataSource_RunsByCommitShas') {
|
||||
obj.result.data = {
|
||||
'cloudProjectBySlug': {
|
||||
'__typename': 'CloudProject',
|
||||
'id': 'Q2xvdWRQcm9qZWN0OnZncXJ3cA==',
|
||||
'runsByCommitShas': [
|
||||
{
|
||||
'id': 'Q2xvdWRSdW46TUdWZXhvQkRPNg==',
|
||||
'runNumber': 136,
|
||||
'status': 'FAILED',
|
||||
'commitInfo': {
|
||||
'sha': 'commit2',
|
||||
'__typename': 'CloudRunCommitInfo',
|
||||
},
|
||||
'__typename': 'CloudRun',
|
||||
},
|
||||
{
|
||||
'id': 'Q2xvdWRSdW46ckdXb2wzbzJHVg==',
|
||||
'runNumber': 134,
|
||||
'status': 'PASSED',
|
||||
'commitInfo': {
|
||||
'sha': '37fa5bfb9e774d00a03fe8f0d439f06ec70f533d',
|
||||
'__typename': 'CloudRunCommitInfo',
|
||||
},
|
||||
'__typename': 'CloudRun',
|
||||
},
|
||||
],
|
||||
},
|
||||
'pollingIntervals': {
|
||||
'runsByCommitShas': 30,
|
||||
'__typename': 'CloudPollingIntervals',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return obj.result
|
||||
})
|
||||
|
||||
@@ -50,13 +84,10 @@ describe('App: Spec List - Flaky Indicator', () => {
|
||||
__typename: 'CloudProjectSpec',
|
||||
id: `id${obj.variables.specPath}`,
|
||||
retrievedAt: new Date().toISOString(),
|
||||
averageDuration: null,
|
||||
specRuns: {
|
||||
__typename: 'CloudSpecRunConnection',
|
||||
nodes: [],
|
||||
},
|
||||
isConsideredFlaky: true,
|
||||
flakyStatus: {
|
||||
averageDurationForRunIds: null,
|
||||
specRunsForRunIds: [],
|
||||
isConsideredFlakyForRunIds: true,
|
||||
flakyStatusForRunIds: {
|
||||
__typename: 'CloudProjectSpecFlakyStatus',
|
||||
severity: 'LOW',
|
||||
flakyRuns: 2,
|
||||
@@ -71,21 +102,10 @@ describe('App: Spec List - Flaky Indicator', () => {
|
||||
__typename: 'CloudProjectSpec',
|
||||
id: `id${obj.variables.specPath}`,
|
||||
retrievedAt: new Date().toISOString(),
|
||||
averageDuration: null,
|
||||
specRuns: {
|
||||
__typename: 'CloudSpecRunConnection',
|
||||
nodes: [],
|
||||
},
|
||||
isConsideredFlaky: false,
|
||||
flakyStatus: null,
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.field === 'cloudLatestRunUpdateSpecData') {
|
||||
return {
|
||||
__typename: 'CloudLatestRunUpdateSpecData',
|
||||
mostRecentUpdate: new Date('2022-06-10').toISOString(),
|
||||
pollingInterval: 60,
|
||||
averageDurationForRunIds: null,
|
||||
specRunsForRunIds: [],
|
||||
isConsideredFlakyForRunIds: false,
|
||||
flakyStatusForRunIds: null,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ function specShouldShow (specFileName: string, runDotsClasses: string[], latestR
|
||||
const latestStatusSpinning = latestRunStatus === 'RUNNING'
|
||||
|
||||
type dotIndex = Parameters<typeof dotSelector>[1];
|
||||
const indexes: dotIndex[] = [0, 1, 2]
|
||||
const indexes: Exclude<dotIndex, 'latest'>[] = [0, 1, 2]
|
||||
|
||||
indexes.forEach((i) => {
|
||||
return cy.get(dotSelector(specFileName, i)).should('have.class', `icon-light-${runDotsClasses.length > i ? runDotsClasses[i] : 'gray-300'}`)
|
||||
@@ -66,6 +66,45 @@ function specShouldShow (specFileName: string, runDotsClasses: string[], latestR
|
||||
}
|
||||
|
||||
function simulateRunData () {
|
||||
cy.remoteGraphQLIntercept(async (obj) => {
|
||||
if (obj.operationName === 'RelevantRunsDataSource_RunsByCommitShas') {
|
||||
obj.result.data = {
|
||||
'cloudProjectBySlug': {
|
||||
'__typename': 'CloudProject',
|
||||
'id': 'Q2xvdWRQcm9qZWN0OnZncXJ3cA==',
|
||||
'runsByCommitShas': [
|
||||
{
|
||||
'id': 'Q2xvdWRSdW46TUdWZXhvQkRPNg==',
|
||||
'runNumber': 136,
|
||||
'status': 'FAILED',
|
||||
'commitInfo': {
|
||||
'sha': 'commit2',
|
||||
'__typename': 'CloudRunCommitInfo',
|
||||
},
|
||||
'__typename': 'CloudRun',
|
||||
},
|
||||
{
|
||||
'id': 'Q2xvdWRSdW46ckdXb2wzbzJHVg==',
|
||||
'runNumber': 134,
|
||||
'status': 'PASSED',
|
||||
'commitInfo': {
|
||||
'sha': '37fa5bfb9e774d00a03fe8f0d439f06ec70f533d',
|
||||
'__typename': 'CloudRunCommitInfo',
|
||||
},
|
||||
'__typename': 'CloudRun',
|
||||
},
|
||||
],
|
||||
},
|
||||
'pollingIntervals': {
|
||||
'runsByCommitShas': 30,
|
||||
'__typename': 'CloudPollingIntervals',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return obj.result
|
||||
})
|
||||
|
||||
cy.remoteGraphQLInterceptBatched(async (obj) => {
|
||||
if (obj.field !== 'cloudSpecByPath') {
|
||||
return obj.result
|
||||
@@ -132,11 +171,8 @@ function simulateRunData () {
|
||||
__typename: 'CloudProjectSpec',
|
||||
retrievedAt: new Date().toISOString(),
|
||||
id: `id${obj.variables.specPath}`,
|
||||
specRuns: {
|
||||
__typename: 'CloudSpecRunConnection',
|
||||
nodes: runs,
|
||||
},
|
||||
averageDuration,
|
||||
specRunsForRunIds: runs,
|
||||
averageDurationForRunIds: averageDuration,
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -159,6 +195,7 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
|
||||
|
||||
cy.withCtx((ctx, o) => {
|
||||
o.sinon.stub(ctx.lifecycleManager.git!, 'currentBranch').value('fakeBranch')
|
||||
ctx.git?.__setGitHashesForTesting(['commit1', 'commit2'])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -410,15 +447,55 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
|
||||
cy.remoteGraphQLIntercept(async (obj, testState) => {
|
||||
const pollingCounter = testState.pollingCounter ?? 0
|
||||
|
||||
if (obj.result.data && 'cloudLatestRunUpdateSpecData' in obj.result.data) {
|
||||
const mostRecentUpdate = pollingCounter > 1 ? new Date().toISOString() : new Date('2022-06-10').toISOString()
|
||||
// initial polling interval is set to every second to avoid long wait times
|
||||
const pollingInterval = pollingCounter > 1 ? 30 : 1
|
||||
if (obj.operationName === 'RelevantRunsDataSource_RunsByCommitShas') {
|
||||
obj.result.data = {
|
||||
'cloudProjectBySlug': {
|
||||
'__typename': 'CloudProject',
|
||||
'id': 'Q2xvdWRQcm9qZWN0OnZncXJ3cA==',
|
||||
'runsByCommitShas': [
|
||||
{
|
||||
'id': 'Q2xvdWRSdW46TUdWZXhvQkRPNg==',
|
||||
'runNumber': 136,
|
||||
'status': 'PASSED',
|
||||
'commitInfo': {
|
||||
'sha': 'commit2',
|
||||
'__typename': 'CloudRunCommitInfo',
|
||||
},
|
||||
'__typename': 'CloudRun',
|
||||
},
|
||||
{
|
||||
'id': 'Q2xvdWRSdW46ckdXb2wzbzJHVg==',
|
||||
'runNumber': 134,
|
||||
'status': 'FAILED',
|
||||
'commitInfo': {
|
||||
'sha': '37fa5bfb9e774d00a03fe8f0d439f06ec70f533d',
|
||||
'__typename': 'CloudRunCommitInfo',
|
||||
},
|
||||
'__typename': 'CloudRun',
|
||||
},
|
||||
],
|
||||
},
|
||||
'pollingIntervals': {
|
||||
'runsByCommitShas': 1,
|
||||
'__typename': 'CloudPollingIntervals',
|
||||
},
|
||||
}
|
||||
|
||||
obj.result.data.cloudLatestRunUpdateSpecData = {
|
||||
__typename: 'CloudLatestRunUpdateSpecData',
|
||||
mostRecentUpdate,
|
||||
pollingInterval,
|
||||
if (pollingCounter > 2) {
|
||||
obj.result.data.cloudProjectBySlug.runsByCommitShas.shift({
|
||||
'id': 'Q2xvdWRSdW46TUdWZXhvQkRPNg==',
|
||||
'runNumber': 138,
|
||||
'status': 'FAILED',
|
||||
'commitInfo': {
|
||||
'sha': 'commit2',
|
||||
'__typename': 'CloudRunCommitInfo',
|
||||
},
|
||||
'__typename': 'CloudRun',
|
||||
})
|
||||
}
|
||||
|
||||
if (pollingCounter > 5) {
|
||||
obj.result.data.pollingIntervals.runsByCommitShas = 100
|
||||
}
|
||||
|
||||
testState.pollingCounter = pollingCounter + 1
|
||||
@@ -488,11 +565,8 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
|
||||
__typename: 'CloudProjectSpec',
|
||||
retrievedAt: new Date().toISOString(),
|
||||
id: `id${obj.variables.specPath}`,
|
||||
specRuns: {
|
||||
__typename: 'CloudSpecRunConnection',
|
||||
nodes: runs,
|
||||
},
|
||||
averageDuration,
|
||||
specRunsForRunIds: runs,
|
||||
averageDurationForRunIds: averageDuration,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -519,125 +593,6 @@ describe('App/Cloud Integration - Latest runs and Average duration', { viewportW
|
||||
cy.get(averageDurationSelector('accounts_list.spec.js')).contains('0:13')
|
||||
})
|
||||
})
|
||||
|
||||
context('polling indicates no new data', () => {
|
||||
beforeEach(() => {
|
||||
cy.loginUser()
|
||||
|
||||
cy.remoteGraphQLIntercept(async (obj, testState) => {
|
||||
const pollingCounter = testState.pollingCounter ?? 0
|
||||
|
||||
if (obj.result.data && 'cloudLatestRunUpdateSpecData' in obj.result.data) {
|
||||
const mostRecentUpdate = new Date('2022-06-10').toISOString()
|
||||
// initial polling interval is set to every second to avoid long wait times
|
||||
const pollingInterval = pollingCounter > 1 ? 30 : 1
|
||||
|
||||
obj.result.data.cloudLatestRunUpdateSpecData = {
|
||||
__typename: 'CloudLatestRunUpdateSpecData',
|
||||
mostRecentUpdate,
|
||||
pollingInterval,
|
||||
}
|
||||
|
||||
testState.pollingCounter = pollingCounter + 1
|
||||
}
|
||||
|
||||
return obj.result
|
||||
})
|
||||
|
||||
cy.remoteGraphQLInterceptBatched(async (obj, testState) => {
|
||||
if (obj.field !== 'cloudSpecByPath') {
|
||||
return obj.result
|
||||
}
|
||||
|
||||
const fakeRuns = (statuses: string[], idPrefix: string) => {
|
||||
return statuses.map((s, idx) => {
|
||||
return {
|
||||
__typename: 'CloudSpecRun',
|
||||
id: `SpecRun_${idPrefix}_${idx}`,
|
||||
status: s,
|
||||
createdAt: new Date('2022-05-08T03:17:00').toISOString(),
|
||||
completedAt: new Date('2022-05-08T05:17:00').toISOString(),
|
||||
basename: idPrefix.substring(idPrefix.lastIndexOf('/') + 1, idPrefix.indexOf('.')),
|
||||
path: idPrefix,
|
||||
extension: idPrefix.substring(idPrefix.indexOf('.')),
|
||||
runNumber: 432,
|
||||
groupCount: 2,
|
||||
specDuration: {
|
||||
min: 143003, // 2:23
|
||||
max: 159120, // 3:40
|
||||
__typename: 'SpecDataAggregate',
|
||||
},
|
||||
testsFailed: {
|
||||
min: 1,
|
||||
max: 2,
|
||||
__typename: 'SpecDataAggregate',
|
||||
},
|
||||
testsPassed: {
|
||||
min: 22,
|
||||
max: 23,
|
||||
__typename: 'SpecDataAggregate',
|
||||
},
|
||||
testsSkipped: {
|
||||
min: null,
|
||||
max: null,
|
||||
__typename: 'SpecDataAggregate',
|
||||
},
|
||||
testsPending: {
|
||||
min: 1,
|
||||
max: 2,
|
||||
__typename: 'SpecDataAggregate',
|
||||
},
|
||||
url: 'https://google.com',
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const pollingCounter = testState.pollingCounter ?? 0
|
||||
|
||||
// simulate network latency to allow for caching to register
|
||||
await new Promise((r) => setTimeout(r, 20))
|
||||
|
||||
const statuses = pollingCounter < 2 ? ['PASSED', 'FAILED', 'CANCELLED', 'ERRORED'] : ['FAILED', 'PASSED', 'FAILED', 'CANCELLED', 'ERRORED']
|
||||
const runs = fakeRuns(statuses, obj.variables.specPath)
|
||||
const averageDuration = pollingCounter < 2 ? 12000 : 13000
|
||||
|
||||
return {
|
||||
__typename: 'CloudProjectSpec',
|
||||
retrievedAt: new Date().toISOString(),
|
||||
id: `id${obj.variables.specPath}`,
|
||||
specRuns: {
|
||||
__typename: 'CloudSpecRunConnection',
|
||||
nodes: runs,
|
||||
},
|
||||
averageDuration,
|
||||
}
|
||||
})
|
||||
|
||||
cy.visitApp()
|
||||
cy.findByTestId('sidebar-link-specs-page').click()
|
||||
})
|
||||
|
||||
it('shows the same data after polling', () => {
|
||||
specShouldShow('accounts_list.spec.js', ['orange-400', 'gray-300', 'red-400'], 'PASSED')
|
||||
cy.get(dotSelector('accounts_new.spec.js', 'latest')).trigger('mouseenter')
|
||||
cy.get('.v-popper__popper--shown').should('exist')
|
||||
|
||||
validateTooltip('Passed')
|
||||
cy.get(dotSelector('accounts_new.spec.js', 'latest')).trigger('mouseleave')
|
||||
cy.get(averageDurationSelector('accounts_list.spec.js')).contains('0:12')
|
||||
|
||||
cy.wait(1200)
|
||||
|
||||
// new results should be shown
|
||||
specShouldShow('accounts_list.spec.js', ['orange-400', 'gray-300', 'red-400'], 'PASSED')
|
||||
cy.get(dotSelector('accounts_new.spec.js', 'latest')).trigger('mouseenter')
|
||||
cy.get('.v-popper__popper--shown').should('exist')
|
||||
|
||||
validateTooltip('Passed')
|
||||
cy.get(dotSelector('accounts_new.spec.js', 'latest')).trigger('mouseleave')
|
||||
cy.get(averageDurationSelector('accounts_list.spec.js')).contains('0:12')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('App/Cloud Integration - Latest runs and Average duration', { viewportWidth: 1200 }, () => {
|
||||
|
||||
@@ -1,462 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"cloudViewer": {
|
||||
"id": "Q2xvdWRVc2VyOjcxYTM3NmVhLTdlMGUtNDBhOS1hMTAzLWMwM2NmNTMyMmQyZg==",
|
||||
"fullName": "Lachlan Miller",
|
||||
"email": "lachlan.miller.1990@outlook.com",
|
||||
"firstOrganization": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRPcmdhbml6YXRpb246NjE5ODJiMmItOTRmNy00ZjYzLTlmYjctNGI1MTc4NjQ5OWJh",
|
||||
"__typename": "CloudOrganization"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudOrganizationConnection"
|
||||
},
|
||||
"__typename": "CloudUser"
|
||||
},
|
||||
"cachedUser": {
|
||||
"id": "Q2FjaGVkVXNlcjpsYWNobGFuLm1pbGxlci4xOTkwQG91dGxvb2suY29t",
|
||||
"fullName": "Lachlan Miller",
|
||||
"email": "lachlan.miller.1990@outlook.com",
|
||||
"__typename": "CachedUser"
|
||||
},
|
||||
"authState": {
|
||||
"name": null,
|
||||
"__typename": "AuthState"
|
||||
},
|
||||
"currentProject": {
|
||||
"id": "debug-test-project-id",
|
||||
"config": [
|
||||
{
|
||||
"value": 5,
|
||||
"from": "default",
|
||||
"field": "animationDistanceThreshold"
|
||||
},
|
||||
{
|
||||
"value": "arm64",
|
||||
"from": "default",
|
||||
"field": "arch"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "baseUrl"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "blockHosts"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "chromeWebSecurity"
|
||||
},
|
||||
{
|
||||
"value": [],
|
||||
"from": "default",
|
||||
"field": "clientCertificates"
|
||||
},
|
||||
{
|
||||
"value": 4000,
|
||||
"from": "default",
|
||||
"field": "defaultCommandTimeout"
|
||||
},
|
||||
{
|
||||
"value": "cypress/downloads",
|
||||
"from": "default",
|
||||
"field": "downloadsFolder"
|
||||
},
|
||||
{
|
||||
"value": {
|
||||
"INTERNAL_CLOUD_ENV": "production",
|
||||
"INTERNAL_GRAPHQL_PORT": 4444,
|
||||
"INTERNAL_EVENT_COLLECTOR_ENV": "staging",
|
||||
"CONFIG_ENV": "production"
|
||||
},
|
||||
"field": "env",
|
||||
"from": "env"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "execTimeout"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalFetchPolyfill"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalInteractiveRunEvents"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalRunAllSpecs"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalMemoryManagement"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalModifyObstructiveThirdPartyCode"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "experimentalSkipDomainInjection"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalOriginDependencies"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalSourceRewriting"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "config",
|
||||
"field": "experimentalSingleTabRunMode"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalStudio"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalWebKitSupport"
|
||||
},
|
||||
{
|
||||
"value": "",
|
||||
"from": "default",
|
||||
"field": "fileServerFolder"
|
||||
},
|
||||
{
|
||||
"value": "cypress/fixtures",
|
||||
"from": "default",
|
||||
"field": "fixturesFolder"
|
||||
},
|
||||
{
|
||||
"value": [
|
||||
"**/__snapshots__/*",
|
||||
"**/__image_snapshots__/*"
|
||||
],
|
||||
"from": "default",
|
||||
"field": "excludeSpecPattern"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "includeShadowDom"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"from": "default",
|
||||
"field": "keystrokeDelay"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "modifyObstructiveCode"
|
||||
},
|
||||
{
|
||||
"from": "default",
|
||||
"field": "nodeVersion"
|
||||
},
|
||||
{
|
||||
"value": 50,
|
||||
"from": "default",
|
||||
"field": "numTestsKeptInMemory"
|
||||
},
|
||||
{
|
||||
"value": "darwin",
|
||||
"from": "default",
|
||||
"field": "platform"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "pageLoadTimeout"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "port"
|
||||
},
|
||||
{
|
||||
"value": "vgqrwp",
|
||||
"from": "config",
|
||||
"field": "projectId"
|
||||
},
|
||||
{
|
||||
"value": 20,
|
||||
"from": "default",
|
||||
"field": "redirectionLimit"
|
||||
},
|
||||
{
|
||||
"value": "spec",
|
||||
"from": "default",
|
||||
"field": "reporter"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "reporterOptions"
|
||||
},
|
||||
{
|
||||
"value": 5000,
|
||||
"from": "default",
|
||||
"field": "requestTimeout"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "resolvedNodePath"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "resolvedNodeVersion"
|
||||
},
|
||||
{
|
||||
"value": 30000,
|
||||
"from": "default",
|
||||
"field": "responseTimeout"
|
||||
},
|
||||
{
|
||||
"value": {
|
||||
"runMode": 0,
|
||||
"openMode": 0
|
||||
},
|
||||
"from": "default",
|
||||
"field": "retries"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "screenshotOnRunFailure"
|
||||
},
|
||||
{
|
||||
"value": "cypress/screenshots",
|
||||
"from": "default",
|
||||
"field": "screenshotsFolder"
|
||||
},
|
||||
{
|
||||
"value": 250,
|
||||
"from": "default",
|
||||
"field": "slowTestThreshold"
|
||||
},
|
||||
{
|
||||
"value": "top",
|
||||
"from": "default",
|
||||
"field": "scrollBehavior"
|
||||
},
|
||||
{
|
||||
"value": "cypress/support/component.{js,jsx,ts,tsx}",
|
||||
"from": "default",
|
||||
"field": "supportFile"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "supportFolder"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "taskTimeout"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "testIsolation"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "trashAssetsBeforeRuns"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "userAgent"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "video"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "videoCompression"
|
||||
},
|
||||
{
|
||||
"value": "cypress/videos",
|
||||
"from": "default",
|
||||
"field": "videosFolder"
|
||||
},
|
||||
{
|
||||
"value": 500,
|
||||
"from": "default",
|
||||
"field": "viewportHeight"
|
||||
},
|
||||
{
|
||||
"value": 500,
|
||||
"from": "default",
|
||||
"field": "viewportWidth"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "waitForAnimations"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "watchForFileChanges"
|
||||
},
|
||||
{
|
||||
"value": "**/*.cy.{js,jsx,ts,tsx}",
|
||||
"from": "default",
|
||||
"field": "specPattern"
|
||||
},
|
||||
{
|
||||
"value": [
|
||||
{
|
||||
"name": "chrome",
|
||||
"family": "chromium",
|
||||
"channel": "stable",
|
||||
"displayName": "Chrome",
|
||||
"version": "109.0.5414.119",
|
||||
"path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
"minSupportedVersion": 64,
|
||||
"majorVersion": "109"
|
||||
},
|
||||
{
|
||||
"name": "firefox",
|
||||
"family": "firefox",
|
||||
"channel": "stable",
|
||||
"displayName": "Firefox",
|
||||
"version": "107.0.1",
|
||||
"path": "/Applications/Firefox.app/Contents/MacOS/firefox",
|
||||
"minSupportedVersion": 86,
|
||||
"majorVersion": "107"
|
||||
},
|
||||
{
|
||||
"name": "electron",
|
||||
"channel": "stable",
|
||||
"family": "chromium",
|
||||
"displayName": "Electron",
|
||||
"version": "106.0.5249.51",
|
||||
"path": "",
|
||||
"majorVersion": 106
|
||||
}
|
||||
],
|
||||
"from": "runtime",
|
||||
"field": "browsers"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "hosts"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "isInteractive"
|
||||
}
|
||||
],
|
||||
"isFullConfigReady": true,
|
||||
"hasNonExampleSpec": true,
|
||||
"savedState": {
|
||||
"firstOpened": 1674605493218,
|
||||
"lastOpened": 1675053721981,
|
||||
"lastProjectId": "vgqrwp",
|
||||
"specFilter": ""
|
||||
},
|
||||
"cloudProject": {
|
||||
"__typename": "CloudProject",
|
||||
"id": "cloud-project-test-id",
|
||||
"runs": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRSdW46TUdWZXhvQkRPNg==",
|
||||
"status": "FAILED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/136",
|
||||
"__typename": "CloudRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRSdW46Nk9kdm93eG45cQ==",
|
||||
"status": "FAILED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/135",
|
||||
"__typename": "CloudRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRSdW46ckdXb2wzbzJHVg==",
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/134",
|
||||
"__typename": "CloudRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRSdW46WUc0eDVZMFZHUA==",
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/133",
|
||||
"__typename": "CloudRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRSdW46VjkxMHJvRGpHcg==",
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/132",
|
||||
"__typename": "CloudRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRSdW46ZU9qeWtCUFlMcQ==",
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/131",
|
||||
"__typename": "CloudRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRSdW46ajl4bjhYV05PbA==",
|
||||
"status": "FAILED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/130",
|
||||
"__typename": "CloudRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRSdW46a0wzRVBlNTBHdw==",
|
||||
"status": "FAILED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/129",
|
||||
"__typename": "CloudRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRSdW46Vk9KNnhkVmVPYg==",
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/128",
|
||||
"__typename": "CloudRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRSdW46SzlFTlEyb05MYg==",
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/127",
|
||||
"__typename": "CloudRun"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudRunConnection"
|
||||
}
|
||||
},
|
||||
"__typename": "CurrentProject"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,10 +78,10 @@
|
||||
"specs": [
|
||||
{
|
||||
"id": "Q2xvdWRTcGVjUnVuOmY0YzE3OGIxLWRlZjktNGI1NC1hOTU1LWQ3MGU0NDhjMTg5MTpNekExTlRVNE1UWXRNalZqTmkxak0yWmlMVEU0WWpFdFkyWTVaV1JrWkRFM05qTmk=",
|
||||
"path": "src/components/InfoPanel/InfoPanel.cy.ts",
|
||||
"basename": "InfoPanel.cy.ts",
|
||||
"extension": ".cy.ts",
|
||||
"shortPath": "src/components/InfoPanel/InfoPanel.cy.ts",
|
||||
"path": "src/NewComponent.spec.jsx",
|
||||
"basename": "NewComponent.spec.jsx",
|
||||
"extension": ".spec.jsx",
|
||||
"shortPath": "src/NewComponent.spec.jsx",
|
||||
"groupIds": [
|
||||
"Q2xvdWRSdW5Hcm91cDo2Njg2MTI4NjpsaW51eC1FbGVjdHJvbi0xMDYtYjAyZTk4NDJiNQ=="
|
||||
],
|
||||
|
||||
@@ -1,656 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"currentProject": {
|
||||
"id": "Q3VycmVudFByb2plY3Q6L1VzZXJzL2xhY2hsYW5taWxsZXIvY29kZS9kdW1wL2VsZXV0aGVyaWEvcGFja2FnZXMvZnJvbnRlbmQ=",
|
||||
"title": "frontend",
|
||||
"config": [
|
||||
{
|
||||
"value": 5,
|
||||
"from": "default",
|
||||
"field": "animationDistanceThreshold"
|
||||
},
|
||||
{
|
||||
"value": "arm64",
|
||||
"from": "default",
|
||||
"field": "arch"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "baseUrl"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "blockHosts"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "chromeWebSecurity"
|
||||
},
|
||||
{
|
||||
"value": [],
|
||||
"from": "default",
|
||||
"field": "clientCertificates"
|
||||
},
|
||||
{
|
||||
"value": 4000,
|
||||
"from": "default",
|
||||
"field": "defaultCommandTimeout"
|
||||
},
|
||||
{
|
||||
"value": "cypress/downloads",
|
||||
"from": "default",
|
||||
"field": "downloadsFolder"
|
||||
},
|
||||
{
|
||||
"value": {
|
||||
"INTERNAL_CLOUD_ENV": "production",
|
||||
"INTERNAL_GRAPHQL_PORT": 4444,
|
||||
"INTERNAL_EVENT_COLLECTOR_ENV": "staging",
|
||||
"CONFIG_ENV": "production"
|
||||
},
|
||||
"field": "env",
|
||||
"from": "env"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "execTimeout"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalFetchPolyfill"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalInteractiveRunEvents"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalRunAllSpecs"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalMemoryManagement"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalModifyObstructiveThirdPartyCode"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "experimentalSkipDomainInjection"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalOriginDependencies"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalSourceRewriting"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "config",
|
||||
"field": "experimentalSingleTabRunMode"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalStudio"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalWebKitSupport"
|
||||
},
|
||||
{
|
||||
"value": "",
|
||||
"from": "default",
|
||||
"field": "fileServerFolder"
|
||||
},
|
||||
{
|
||||
"value": "cypress/fixtures",
|
||||
"from": "default",
|
||||
"field": "fixturesFolder"
|
||||
},
|
||||
{
|
||||
"value": [
|
||||
"**/__snapshots__/*",
|
||||
"**/__image_snapshots__/*"
|
||||
],
|
||||
"from": "default",
|
||||
"field": "excludeSpecPattern"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "includeShadowDom"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"from": "default",
|
||||
"field": "keystrokeDelay"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "modifyObstructiveCode"
|
||||
},
|
||||
{
|
||||
"from": "default",
|
||||
"field": "nodeVersion"
|
||||
},
|
||||
{
|
||||
"value": 50,
|
||||
"from": "default",
|
||||
"field": "numTestsKeptInMemory"
|
||||
},
|
||||
{
|
||||
"value": "darwin",
|
||||
"from": "default",
|
||||
"field": "platform"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "pageLoadTimeout"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "port"
|
||||
},
|
||||
{
|
||||
"value": "vgqrwp",
|
||||
"from": "config",
|
||||
"field": "projectId"
|
||||
},
|
||||
{
|
||||
"value": 20,
|
||||
"from": "default",
|
||||
"field": "redirectionLimit"
|
||||
},
|
||||
{
|
||||
"value": "spec",
|
||||
"from": "default",
|
||||
"field": "reporter"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "reporterOptions"
|
||||
},
|
||||
{
|
||||
"value": 5000,
|
||||
"from": "default",
|
||||
"field": "requestTimeout"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "resolvedNodePath"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "resolvedNodeVersion"
|
||||
},
|
||||
{
|
||||
"value": 30000,
|
||||
"from": "default",
|
||||
"field": "responseTimeout"
|
||||
},
|
||||
{
|
||||
"value": {
|
||||
"runMode": 0,
|
||||
"openMode": 0
|
||||
},
|
||||
"from": "default",
|
||||
"field": "retries"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "screenshotOnRunFailure"
|
||||
},
|
||||
{
|
||||
"value": "cypress/screenshots",
|
||||
"from": "default",
|
||||
"field": "screenshotsFolder"
|
||||
},
|
||||
{
|
||||
"value": 250,
|
||||
"from": "default",
|
||||
"field": "slowTestThreshold"
|
||||
},
|
||||
{
|
||||
"value": "top",
|
||||
"from": "default",
|
||||
"field": "scrollBehavior"
|
||||
},
|
||||
{
|
||||
"value": "cypress/support/component.{js,jsx,ts,tsx}",
|
||||
"from": "default",
|
||||
"field": "supportFile"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "supportFolder"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "taskTimeout"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "testIsolation"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "trashAssetsBeforeRuns"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "userAgent"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "video"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "videoCompression"
|
||||
},
|
||||
{
|
||||
"value": "cypress/videos",
|
||||
"from": "default",
|
||||
"field": "videosFolder"
|
||||
},
|
||||
{
|
||||
"value": 500,
|
||||
"from": "default",
|
||||
"field": "viewportHeight"
|
||||
},
|
||||
{
|
||||
"value": 500,
|
||||
"from": "default",
|
||||
"field": "viewportWidth"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "waitForAnimations"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "watchForFileChanges"
|
||||
},
|
||||
{
|
||||
"value": "**/*.cy.{js,jsx,ts,tsx}",
|
||||
"from": "default",
|
||||
"field": "specPattern"
|
||||
},
|
||||
{
|
||||
"value": [
|
||||
{
|
||||
"name": "chrome",
|
||||
"family": "chromium",
|
||||
"channel": "stable",
|
||||
"displayName": "Chrome",
|
||||
"version": "109.0.5414.119",
|
||||
"path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
"minSupportedVersion": 64,
|
||||
"majorVersion": "109"
|
||||
},
|
||||
{
|
||||
"name": "firefox",
|
||||
"family": "firefox",
|
||||
"channel": "stable",
|
||||
"displayName": "Firefox",
|
||||
"version": "107.0.1",
|
||||
"path": "/Applications/Firefox.app/Contents/MacOS/firefox",
|
||||
"minSupportedVersion": 86,
|
||||
"majorVersion": "107"
|
||||
},
|
||||
{
|
||||
"name": "electron",
|
||||
"channel": "stable",
|
||||
"family": "chromium",
|
||||
"displayName": "Electron",
|
||||
"version": "106.0.5249.51",
|
||||
"path": "",
|
||||
"majorVersion": 106
|
||||
}
|
||||
],
|
||||
"from": "runtime",
|
||||
"field": "browsers"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "hosts"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "isInteractive"
|
||||
}
|
||||
],
|
||||
"savedState": {
|
||||
"firstOpened": 1674605493218,
|
||||
"lastOpened": 1675053721981,
|
||||
"lastProjectId": "vgqrwp",
|
||||
"specFilter": ""
|
||||
},
|
||||
"currentTestingType": "component",
|
||||
"branch": "main",
|
||||
"packageManager": "yarn",
|
||||
"activeBrowser": {
|
||||
"id": "QnJvd3NlcjpjaHJvbWUtY2hyb21pdW0tc3RhYmxl",
|
||||
"displayName": "Chrome",
|
||||
"majorVersion": "109",
|
||||
"__typename": "Browser"
|
||||
},
|
||||
"browsers": [
|
||||
{
|
||||
"id": "QnJvd3NlcjpjaHJvbWUtY2hyb21pdW0tc3RhYmxl",
|
||||
"isSelected": true,
|
||||
"displayName": "Chrome",
|
||||
"version": "109.0.5414.119",
|
||||
"majorVersion": "109",
|
||||
"isVersionSupported": true,
|
||||
"warning": null,
|
||||
"disabled": null,
|
||||
"__typename": "Browser"
|
||||
},
|
||||
{
|
||||
"id": "QnJvd3NlcjpmaXJlZm94LWZpcmVmb3gtc3RhYmxl",
|
||||
"isSelected": false,
|
||||
"displayName": "Firefox",
|
||||
"version": "107.0.1",
|
||||
"majorVersion": "107",
|
||||
"isVersionSupported": true,
|
||||
"warning": null,
|
||||
"disabled": null,
|
||||
"__typename": "Browser"
|
||||
},
|
||||
{
|
||||
"id": "QnJvd3NlcjplbGVjdHJvbi1jaHJvbWl1bS1zdGFibGU=",
|
||||
"isSelected": false,
|
||||
"displayName": "Electron",
|
||||
"version": "106.0.5249.51",
|
||||
"majorVersion": "106",
|
||||
"isVersionSupported": true,
|
||||
"warning": null,
|
||||
"disabled": null,
|
||||
"__typename": "Browser"
|
||||
}
|
||||
],
|
||||
"projectId": "vgqrwp",
|
||||
"cloudProject": {
|
||||
"__typename": "CloudProject",
|
||||
"id": "Q2xvdWRQcm9qZWN0OnZncXJ3cA=="
|
||||
},
|
||||
"__typename": "CurrentProject"
|
||||
},
|
||||
"isGlobalMode": true,
|
||||
"versions": {
|
||||
"current": {
|
||||
"id": "12.4.0",
|
||||
"version": "12.4.0",
|
||||
"released": "2023-01-24T18:40:53.125Z",
|
||||
"__typename": "Version"
|
||||
},
|
||||
"latest": {
|
||||
"id": "12.4.1",
|
||||
"version": "12.4.1",
|
||||
"released": "2023-01-27T15:00:32.366Z",
|
||||
"__typename": "Version"
|
||||
},
|
||||
"__typename": "VersionData"
|
||||
},
|
||||
"cloudViewer": {
|
||||
"id": "Q2xvdWRVc2VyOjcxYTM3NmVhLTdlMGUtNDBhOS1hMTAzLWMwM2NmNTMyMmQyZg==",
|
||||
"cloudOrganizationsUrl": "https://cloud.cypress.io/organizations",
|
||||
"organizations": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRPcmdhbml6YXRpb246NjE5ODJiMmItOTRmNy00ZjYzLTlmYjctNGI1MTc4NjQ5OWJh",
|
||||
"name": "Org 2",
|
||||
"projects": {
|
||||
"nodes": [],
|
||||
"__typename": "CloudProjectConnection"
|
||||
},
|
||||
"__typename": "CloudOrganization"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRPcmdhbml6YXRpb246MDIxZmVhNjctZDYwOC00YWIyLWFmMTctM2Y4YTJhMjNkMDE5",
|
||||
"name": "Lachlan's Personal Projects",
|
||||
"projects": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OnZncXJ3cA==",
|
||||
"slug": "vgqrwp",
|
||||
"name": "Rhythm Game",
|
||||
"__typename": "CloudProject"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudProjectConnection"
|
||||
},
|
||||
"__typename": "CloudOrganization"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRPcmdhbml6YXRpb246ODllYmMwOTktNzhjMS00YjIzLWIwYzMtNjAzMGY0MjAxNDBj",
|
||||
"name": "Lachlan Miller",
|
||||
"projects": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Om9mODhoNQ==",
|
||||
"slug": "of88h5",
|
||||
"name": "baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Onp5N2dzZQ==",
|
||||
"slug": "zy7gse",
|
||||
"name": "express",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmZ1aDkzOQ==",
|
||||
"slug": "fuh939",
|
||||
"name": "bannerjs",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OjVicHF0MQ==",
|
||||
"slug": "5bpqt1",
|
||||
"name": "baretest88",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OjJ5dm1odQ==",
|
||||
"slug": "2yvmhu",
|
||||
"name": "baretest414141",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Ojk4dzhveQ==",
|
||||
"slug": "98w8oy",
|
||||
"name": "desktop-gui-testing",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmJqdWJjYQ==",
|
||||
"slug": "bjubca",
|
||||
"name": "baretest58",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmQ4ZjM5bQ==",
|
||||
"slug": "d8f39m",
|
||||
"name": "baretest00",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmR3am5vMg==",
|
||||
"slug": "dwjno2",
|
||||
"name": "baretest66",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmZ3ZHZ1Mw==",
|
||||
"slug": "fwdvu3",
|
||||
"name": "31baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OnVxNHhyYg==",
|
||||
"slug": "uq4xrb",
|
||||
"name": "baretest33331",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Ong5Y3BzOQ==",
|
||||
"slug": "x9cps9",
|
||||
"name": "555baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmZ6bW53Yw==",
|
||||
"slug": "fzmnwc",
|
||||
"name": "baretestdd",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OnU5Y3d2Zg==",
|
||||
"slug": "u9cwvf",
|
||||
"name": "baretest-41",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Om9rZDQ3OA==",
|
||||
"slug": "okd478",
|
||||
"name": "baretest-1231",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OjkxNTZiMw==",
|
||||
"slug": "9156b3",
|
||||
"name": "baretest555",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmlvbmNhbg==",
|
||||
"slug": "ioncan",
|
||||
"name": "baretest-asdf",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OnpuYm9qOQ==",
|
||||
"slug": "znboj9",
|
||||
"name": "baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmljczdteA==",
|
||||
"slug": "ics7mx",
|
||||
"name": "baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OnN1cjRidw==",
|
||||
"slug": "sur4bw",
|
||||
"name": "baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OjF1b2c1eA==",
|
||||
"slug": "1uog5x",
|
||||
"name": "baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Om52MXJ0OA==",
|
||||
"slug": "nv1rt8",
|
||||
"name": "baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmlnM2Nzaw==",
|
||||
"slug": "ig3csk",
|
||||
"name": "baretest-1",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OjhlbWU2MQ==",
|
||||
"slug": "8eme61",
|
||||
"name": "rhythm-frontendddd",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Ojk4anA1Ng==",
|
||||
"slug": "98jp56",
|
||||
"name": "rhythm-frontend",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OjNlNWJwYg==",
|
||||
"slug": "3e5bpb",
|
||||
"name": "Lachlan Miller Testing",
|
||||
"__typename": "CloudProject"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudProjectConnection"
|
||||
},
|
||||
"__typename": "CloudOrganization"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudOrganizationConnection"
|
||||
},
|
||||
"email": "lachlan.miller.1990@outlook.com",
|
||||
"fullName": "Lachlan Miller",
|
||||
"firstOrganization": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRPcmdhbml6YXRpb246NjE5ODJiMmItOTRmNy00ZjYzLTlmYjctNGI1MTc4NjQ5OWJh",
|
||||
"__typename": "CloudOrganization"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudOrganizationConnection"
|
||||
},
|
||||
"__typename": "CloudUser"
|
||||
},
|
||||
"authState": {
|
||||
"browserOpened": false,
|
||||
"name": null,
|
||||
"message": null,
|
||||
"__typename": "AuthState"
|
||||
},
|
||||
"cachedUser": {
|
||||
"id": "Q2FjaGVkVXNlcjpsYWNobGFuLm1pbGxlci4xOTkwQG91dGxvb2suY29t",
|
||||
"fullName": "Lachlan Miller",
|
||||
"email": "lachlan.miller.1990@outlook.com",
|
||||
"__typename": "CachedUser"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"baseError": null,
|
||||
"currentProject": {
|
||||
"id": "debug-test-project-id",
|
||||
"isLoadingConfigFile": false,
|
||||
"isLoadingNodeEvents": false,
|
||||
"__typename": "CurrentProject"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"localSettings": {
|
||||
"preferences": {
|
||||
"isSideNavigationOpen": true,
|
||||
"isSpecsListOpen": false,
|
||||
"autoScrollingEnabled": true,
|
||||
"reporterWidth": 787,
|
||||
"specListWidth": null,
|
||||
"__typename": "LocalSettingsPreferences"
|
||||
},
|
||||
"__typename": "LocalSettings"
|
||||
},
|
||||
"currentProject": {
|
||||
"id": "debug-test-project-id",
|
||||
"cloudProject": {
|
||||
"__typename": "CloudProject",
|
||||
"id": "cloud-project-test-id",
|
||||
"runByNumber": {
|
||||
"id": "Q2xvdWRSdW46TUdWZXhvQkRPNg==",
|
||||
"status": "FAILED",
|
||||
"totalFailed": 1,
|
||||
"__typename": "CloudRun"
|
||||
}
|
||||
},
|
||||
"isCTConfigured": true,
|
||||
"isE2EConfigured": true,
|
||||
"currentTestingType": "component",
|
||||
"title": "frontend",
|
||||
"branch": "main",
|
||||
"__typename": "CurrentProject"
|
||||
},
|
||||
"invokedFromCli": true
|
||||
}
|
||||
}
|
||||
@@ -1,809 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"currentProject": {
|
||||
"id": "debug-test-project-id",
|
||||
"projectRoot": "/Users/lachlanmiller/code/dump/eleutheria/packages/frontend",
|
||||
"currentTestingType": "component",
|
||||
"cloudProject": {
|
||||
"__typename": "CloudProject",
|
||||
"id": "cloud-project-test-id"
|
||||
},
|
||||
"specs": [
|
||||
{
|
||||
"id": "U3BlYzovVXNlcnMvbGFjaGxhbm1pbGxlci9jb2RlL2R1bXAvZWxldXRoZXJpYS9wYWNrYWdlcy9mcm9udGVuZC9zcmMvY29tcG9uZW50cy9EaWZmaWN1bHR5SXRlbS5jeS50cw==",
|
||||
"name": "src/components/DifficultyItem.cy.ts",
|
||||
"specType": "component",
|
||||
"absolute": "/Users/lachlanmiller/code/dump/eleutheria/packages/frontend/src/components/DifficultyItem.cy.ts",
|
||||
"baseName": "DifficultyItem.cy.ts",
|
||||
"fileName": "DifficultyItem",
|
||||
"specFileExtension": ".cy.ts",
|
||||
"fileExtension": ".ts",
|
||||
"relative": "src/components/DifficultyItem.cy.ts",
|
||||
"gitInfo": {
|
||||
"lastModifiedTimestamp": "2022-07-21 19:00:38 +1000",
|
||||
"lastModifiedHumanReadable": "6 months ago",
|
||||
"author": "Lachlan Miller",
|
||||
"statusType": "unmodified",
|
||||
"shortHash": "a33f7f4",
|
||||
"subject": "feat: cover (#7)",
|
||||
"__typename": "GitInfo"
|
||||
},
|
||||
"cloudSpec": {
|
||||
"id": "UmVtb3RlRmV0Y2hhYmxlQ2xvdWRQcm9qZWN0U3BlY1Jlc3VsdDo3YWNkNDI4YzFlMmExMGU2ZWU4YmRhMWZjMTQ4OTE5NzdmZTI0ZTk5OmV5Sm1jbTl0UW5KaGJtTm9Jam9pYldGcGJpSXNJbkJ5YjJwbFkzUlRiSFZuSWpvaWRtZHhjbmR3SWl3aWMzQmxZMUJoZEdnaU9pSnpjbU12WTI5dGNHOXVaVzUwY3k5RWFXWm1hV04xYkhSNVNYUmxiUzVqZVM1MGN5Sjk=",
|
||||
"fetchingStatus": "FETCHED",
|
||||
"data": {
|
||||
"__typename": "CloudProjectSpec",
|
||||
"id": "Q2xvdWRQcm9qZWN0U3BlYzp2Z3Fyd3A6YzNKakwyTnZiWEJ2Ym1WdWRITXZSR2xtWm1samRXeDBlVWwwWlcwdVkza3VkSE09",
|
||||
"retrievedAt": "2023-01-30T04:42:05.607Z",
|
||||
"averageDuration": 200,
|
||||
"isConsideredFlaky": false,
|
||||
"flakyStatus": {
|
||||
"__typename": "CloudFeatureNotEnabled"
|
||||
},
|
||||
"specRuns": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRTcGVjUnVuOmY0YzE3OGIxLWRlZjktNGI1NC1hOTU1LWQ3MGU0NDhjMTg5MTpaalU0TnpJeFltSXROek13T1Mxa05XWXlMV05pT1dNdE5UVTRZemRsTVdKak9HUTE=",
|
||||
"runNumber": 136,
|
||||
"basename": "DifficultyItem.cy.ts",
|
||||
"path": "src/components/DifficultyItem.cy.ts",
|
||||
"extension": ".cy.ts",
|
||||
"testsFailed": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPassed": {
|
||||
"min": 1,
|
||||
"max": 1,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPending": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsSkipped": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"createdAt": "2023-01-30T01:44:09.040Z",
|
||||
"groupCount": 1,
|
||||
"specDuration": {
|
||||
"min": 107,
|
||||
"max": 107,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/136/test-results?specs=%5B%7B%22value%22%3A%22%5B%5C%22418f4eed-fcaf-4305-9624-d93ceed654a4%5C%22%5D%22%2C%22label%22%3A%22src%2Fcomponents%2FDifficultyItem.cy.ts%22%7D%5D",
|
||||
"__typename": "CloudSpecRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRTcGVjUnVuOjMzMjBlMjI0LWFmODktNGEyOS04OWM2LTRkZGUxNWFhZDYwMDpaalU0TnpJeFltSXROek13T1Mxa05XWXlMV05pT1dNdE5UVTRZemRsTVdKak9HUTE=",
|
||||
"runNumber": 134,
|
||||
"basename": "DifficultyItem.cy.ts",
|
||||
"path": "src/components/DifficultyItem.cy.ts",
|
||||
"extension": ".cy.ts",
|
||||
"testsFailed": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPassed": {
|
||||
"min": 1,
|
||||
"max": 1,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPending": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsSkipped": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"createdAt": "2023-01-29T07:08:42.978Z",
|
||||
"groupCount": 1,
|
||||
"specDuration": {
|
||||
"min": 191,
|
||||
"max": 191,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/134/test-results?specs=%5B%7B%22value%22%3A%22%5B%5C%22d77709c2-aeb6-4ee3-9ae6-eaa452b56c2a%5C%22%5D%22%2C%22label%22%3A%22src%2Fcomponents%2FDifficultyItem.cy.ts%22%7D%5D",
|
||||
"__typename": "CloudSpecRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRTcGVjUnVuOmIxYWFlZTNlLWY2N2UtNDYxYS05MDM1LTk2ODBlYzY2YmJmYTpaalU0TnpJeFltSXROek13T1Mxa05XWXlMV05pT1dNdE5UVTRZemRsTVdKak9HUTE=",
|
||||
"runNumber": 133,
|
||||
"basename": "DifficultyItem.cy.ts",
|
||||
"path": "src/components/DifficultyItem.cy.ts",
|
||||
"extension": ".cy.ts",
|
||||
"testsFailed": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPassed": {
|
||||
"min": 1,
|
||||
"max": 1,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPending": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsSkipped": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"createdAt": "2023-01-26T07:23:21.660Z",
|
||||
"groupCount": 1,
|
||||
"specDuration": {
|
||||
"min": 285,
|
||||
"max": 285,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/133/test-results?specs=%5B%7B%22value%22%3A%22%5B%5C%22753121da-5f8c-4ba6-91ae-2a16c3a52440%5C%22%5D%22%2C%22label%22%3A%22src%2Fcomponents%2FDifficultyItem.cy.ts%22%7D%5D",
|
||||
"__typename": "CloudSpecRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRTcGVjUnVuOjJlZWQ5NjY0LWQxNTMtNDEzYS04YmQzLWM2NjA5ZWRkOWIzNzpaalU0TnpJeFltSXROek13T1Mxa05XWXlMV05pT1dNdE5UVTRZemRsTVdKak9HUTE=",
|
||||
"runNumber": 132,
|
||||
"basename": "DifficultyItem.cy.ts",
|
||||
"path": "src/components/DifficultyItem.cy.ts",
|
||||
"extension": ".cy.ts",
|
||||
"testsFailed": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPassed": {
|
||||
"min": 1,
|
||||
"max": 1,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPending": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsSkipped": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"createdAt": "2023-01-26T05:25:07.357Z",
|
||||
"groupCount": 1,
|
||||
"specDuration": {
|
||||
"min": 181,
|
||||
"max": 181,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/132/test-results?specs=%5B%7B%22value%22%3A%22%5B%5C%22be24810d-940c-4dc0-b9e8-a3d65eee64f5%5C%22%5D%22%2C%22label%22%3A%22src%2Fcomponents%2FDifficultyItem.cy.ts%22%7D%5D",
|
||||
"__typename": "CloudSpecRun"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudSpecRunConnection"
|
||||
}
|
||||
},
|
||||
"__typename": "RemoteFetchableCloudProjectSpecResult"
|
||||
},
|
||||
"__typename": "Spec"
|
||||
},
|
||||
{
|
||||
"id": "U3BlYzovVXNlcnMvbGFjaGxhbm1pbGxlci9jb2RlL2R1bXAvZWxldXRoZXJpYS9wYWNrYWdlcy9mcm9udGVuZC9zcmMvY29tcG9uZW50cy9JbmZvUGFuZWwvSW5mb1BhbmVsLmN5LnRz",
|
||||
"name": "src/components/InfoPanel/InfoPanel.cy.ts",
|
||||
"specType": "component",
|
||||
"absolute": "/Users/lachlanmiller/code/dump/eleutheria/packages/frontend/src/components/InfoPanel/InfoPanel.cy.ts",
|
||||
"baseName": "InfoPanel.cy.ts",
|
||||
"fileName": "InfoPanel",
|
||||
"specFileExtension": ".cy.ts",
|
||||
"fileExtension": ".ts",
|
||||
"relative": "src/components/InfoPanel/InfoPanel.cy.ts",
|
||||
"gitInfo": {
|
||||
"lastModifiedTimestamp": "2023-01-30 11:01:22 +1000",
|
||||
"lastModifiedHumanReadable": "4 hours ago",
|
||||
"author": "Lachlan Miller",
|
||||
"statusType": "unmodified",
|
||||
"shortHash": "commit1",
|
||||
"subject": "chore: testing cypress",
|
||||
"__typename": "GitInfo"
|
||||
},
|
||||
"cloudSpec": {
|
||||
"id": "UmVtb3RlRmV0Y2hhYmxlQ2xvdWRQcm9qZWN0U3BlY1Jlc3VsdDo3YWNkNDI4YzFlMmExMGU2ZWU4YmRhMWZjMTQ4OTE5NzdmZTI0ZTk5OmV5Sm1jbTl0UW5KaGJtTm9Jam9pYldGcGJpSXNJbkJ5YjJwbFkzUlRiSFZuSWpvaWRtZHhjbmR3SWl3aWMzQmxZMUJoZEdnaU9pSnpjbU12WTI5dGNHOXVaVzUwY3k5SmJtWnZVR0Z1Wld3dlNXNW1iMUJoYm1Wc0xtTjVMblJ6SW4wPQ==",
|
||||
"fetchingStatus": "FETCHED",
|
||||
"data": {
|
||||
"__typename": "CloudProjectSpec",
|
||||
"id": "Q2xvdWRQcm9qZWN0U3BlYzp2Z3Fyd3A6YzNKakwyTnZiWEJ2Ym1WdWRITXZTVzVtYjFCaGJtVnNMMGx1Wm05UVlXNWxiQzVqZVM1MGN3PT0=",
|
||||
"retrievedAt": "2023-01-30T04:42:05.608Z",
|
||||
"averageDuration": 1440.3,
|
||||
"isConsideredFlaky": false,
|
||||
"flakyStatus": {
|
||||
"__typename": "CloudFeatureNotEnabled"
|
||||
},
|
||||
"specRuns": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRTcGVjUnVuOmY0YzE3OGIxLWRlZjktNGI1NC1hOTU1LWQ3MGU0NDhjMTg5MTpNekExTlRVNE1UWXRNalZqTmkxak0yWmlMVEU0WWpFdFkyWTVaV1JrWkRFM05qTmk=",
|
||||
"runNumber": 136,
|
||||
"basename": "InfoPanel.cy.ts",
|
||||
"path": "src/components/InfoPanel/InfoPanel.cy.ts",
|
||||
"extension": ".cy.ts",
|
||||
"testsFailed": {
|
||||
"min": 1,
|
||||
"max": 1,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPassed": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPending": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsSkipped": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"createdAt": "2023-01-30T01:44:09.040Z",
|
||||
"groupCount": 1,
|
||||
"specDuration": {
|
||||
"min": 4509,
|
||||
"max": 4509,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"status": "FAILED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/136/test-results?specs=%5B%7B%22value%22%3A%22%5B%5C%229728b4a7-b420-403f-92e2-e07ea8506efc%5C%22%5D%22%2C%22label%22%3A%22src%2Fcomponents%2FInfoPanel%2FInfoPanel.cy.ts%22%7D%5D",
|
||||
"__typename": "CloudSpecRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRTcGVjUnVuOjMzMjBlMjI0LWFmODktNGEyOS04OWM2LTRkZGUxNWFhZDYwMDpNekExTlRVNE1UWXRNalZqTmkxak0yWmlMVEU0WWpFdFkyWTVaV1JrWkRFM05qTmk=",
|
||||
"runNumber": 134,
|
||||
"basename": "InfoPanel.cy.ts",
|
||||
"path": "src/components/InfoPanel/InfoPanel.cy.ts",
|
||||
"extension": ".cy.ts",
|
||||
"testsFailed": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPassed": {
|
||||
"min": 1,
|
||||
"max": 1,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPending": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsSkipped": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"createdAt": "2023-01-29T07:08:42.978Z",
|
||||
"groupCount": 1,
|
||||
"specDuration": {
|
||||
"min": 83,
|
||||
"max": 83,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/134/test-results?specs=%5B%7B%22value%22%3A%22%5B%5C%2222a9f323-7052-46ec-ab0e-fa923cf3d705%5C%22%5D%22%2C%22label%22%3A%22src%2Fcomponents%2FInfoPanel%2FInfoPanel.cy.ts%22%7D%5D",
|
||||
"__typename": "CloudSpecRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRTcGVjUnVuOmIxYWFlZTNlLWY2N2UtNDYxYS05MDM1LTk2ODBlYzY2YmJmYTpNekExTlRVNE1UWXRNalZqTmkxak0yWmlMVEU0WWpFdFkyWTVaV1JrWkRFM05qTmk=",
|
||||
"runNumber": 133,
|
||||
"basename": "InfoPanel.cy.ts",
|
||||
"path": "src/components/InfoPanel/InfoPanel.cy.ts",
|
||||
"extension": ".cy.ts",
|
||||
"testsFailed": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPassed": {
|
||||
"min": 1,
|
||||
"max": 1,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPending": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsSkipped": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"createdAt": "2023-01-26T07:23:21.660Z",
|
||||
"groupCount": 1,
|
||||
"specDuration": {
|
||||
"min": 68,
|
||||
"max": 68,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/133/test-results?specs=%5B%7B%22value%22%3A%22%5B%5C%22d8cd0724-591b-4f77-ad75-7209d5c8902e%5C%22%5D%22%2C%22label%22%3A%22src%2Fcomponents%2FInfoPanel%2FInfoPanel.cy.ts%22%7D%5D",
|
||||
"__typename": "CloudSpecRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRTcGVjUnVuOjJlZWQ5NjY0LWQxNTMtNDEzYS04YmQzLWM2NjA5ZWRkOWIzNzpNekExTlRVNE1UWXRNalZqTmkxak0yWmlMVEU0WWpFdFkyWTVaV1JrWkRFM05qTmk=",
|
||||
"runNumber": 132,
|
||||
"basename": "InfoPanel.cy.ts",
|
||||
"path": "src/components/InfoPanel/InfoPanel.cy.ts",
|
||||
"extension": ".cy.ts",
|
||||
"testsFailed": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPassed": {
|
||||
"min": 1,
|
||||
"max": 1,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsPending": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"testsSkipped": {
|
||||
"min": 0,
|
||||
"max": 0,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"createdAt": "2023-01-26T05:25:07.357Z",
|
||||
"groupCount": 1,
|
||||
"specDuration": {
|
||||
"min": 93,
|
||||
"max": 93,
|
||||
"__typename": "SpecDataAggregate"
|
||||
},
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/vgqrwp/runs/132/test-results?specs=%5B%7B%22value%22%3A%22%5B%5C%22a895b0f2-aef4-4d8b-aa5b-4b3fba8abccc%5C%22%5D%22%2C%22label%22%3A%22src%2Fcomponents%2FInfoPanel%2FInfoPanel.cy.ts%22%7D%5D",
|
||||
"__typename": "CloudSpecRun"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudSpecRunConnection"
|
||||
}
|
||||
},
|
||||
"__typename": "RemoteFetchableCloudProjectSpecResult"
|
||||
},
|
||||
"__typename": "Spec"
|
||||
}
|
||||
],
|
||||
"config": [
|
||||
{
|
||||
"value": 5,
|
||||
"from": "default",
|
||||
"field": "animationDistanceThreshold"
|
||||
},
|
||||
{
|
||||
"value": "arm64",
|
||||
"from": "default",
|
||||
"field": "arch"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "baseUrl"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "blockHosts"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "chromeWebSecurity"
|
||||
},
|
||||
{
|
||||
"value": [],
|
||||
"from": "default",
|
||||
"field": "clientCertificates"
|
||||
},
|
||||
{
|
||||
"value": 4000,
|
||||
"from": "default",
|
||||
"field": "defaultCommandTimeout"
|
||||
},
|
||||
{
|
||||
"value": "cypress/downloads",
|
||||
"from": "default",
|
||||
"field": "downloadsFolder"
|
||||
},
|
||||
{
|
||||
"value": {
|
||||
"INTERNAL_CLOUD_ENV": "production",
|
||||
"INTERNAL_GRAPHQL_PORT": 4444,
|
||||
"INTERNAL_EVENT_COLLECTOR_ENV": "staging",
|
||||
"CONFIG_ENV": "production"
|
||||
},
|
||||
"field": "env",
|
||||
"from": "env"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "execTimeout"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalFetchPolyfill"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalInteractiveRunEvents"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalRunAllSpecs"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalMemoryManagement"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalModifyObstructiveThirdPartyCode"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "experimentalSkipDomainInjection"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalOriginDependencies"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalSourceRewriting"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "config",
|
||||
"field": "experimentalSingleTabRunMode"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalStudio"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalWebKitSupport"
|
||||
},
|
||||
{
|
||||
"value": "",
|
||||
"from": "default",
|
||||
"field": "fileServerFolder"
|
||||
},
|
||||
{
|
||||
"value": "cypress/fixtures",
|
||||
"from": "default",
|
||||
"field": "fixturesFolder"
|
||||
},
|
||||
{
|
||||
"value": [
|
||||
"**/__snapshots__/*",
|
||||
"**/__image_snapshots__/*"
|
||||
],
|
||||
"from": "default",
|
||||
"field": "excludeSpecPattern"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "includeShadowDom"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"from": "default",
|
||||
"field": "keystrokeDelay"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "modifyObstructiveCode"
|
||||
},
|
||||
{
|
||||
"from": "default",
|
||||
"field": "nodeVersion"
|
||||
},
|
||||
{
|
||||
"value": 50,
|
||||
"from": "default",
|
||||
"field": "numTestsKeptInMemory"
|
||||
},
|
||||
{
|
||||
"value": "darwin",
|
||||
"from": "default",
|
||||
"field": "platform"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "pageLoadTimeout"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "port"
|
||||
},
|
||||
{
|
||||
"value": "vgqrwp",
|
||||
"from": "config",
|
||||
"field": "projectId"
|
||||
},
|
||||
{
|
||||
"value": 20,
|
||||
"from": "default",
|
||||
"field": "redirectionLimit"
|
||||
},
|
||||
{
|
||||
"value": "spec",
|
||||
"from": "default",
|
||||
"field": "reporter"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "reporterOptions"
|
||||
},
|
||||
{
|
||||
"value": 5000,
|
||||
"from": "default",
|
||||
"field": "requestTimeout"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "resolvedNodePath"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "resolvedNodeVersion"
|
||||
},
|
||||
{
|
||||
"value": 30000,
|
||||
"from": "default",
|
||||
"field": "responseTimeout"
|
||||
},
|
||||
{
|
||||
"value": {
|
||||
"runMode": 0,
|
||||
"openMode": 0
|
||||
},
|
||||
"from": "default",
|
||||
"field": "retries"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "screenshotOnRunFailure"
|
||||
},
|
||||
{
|
||||
"value": "cypress/screenshots",
|
||||
"from": "default",
|
||||
"field": "screenshotsFolder"
|
||||
},
|
||||
{
|
||||
"value": 250,
|
||||
"from": "default",
|
||||
"field": "slowTestThreshold"
|
||||
},
|
||||
{
|
||||
"value": "top",
|
||||
"from": "default",
|
||||
"field": "scrollBehavior"
|
||||
},
|
||||
{
|
||||
"value": "cypress/support/component.{js,jsx,ts,tsx}",
|
||||
"from": "default",
|
||||
"field": "supportFile"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "supportFolder"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "taskTimeout"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "testIsolation"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "trashAssetsBeforeRuns"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "userAgent"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "video"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "videoCompression"
|
||||
},
|
||||
{
|
||||
"value": "cypress/videos",
|
||||
"from": "default",
|
||||
"field": "videosFolder"
|
||||
},
|
||||
{
|
||||
"value": 500,
|
||||
"from": "default",
|
||||
"field": "viewportHeight"
|
||||
},
|
||||
{
|
||||
"value": 500,
|
||||
"from": "default",
|
||||
"field": "viewportWidth"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "waitForAnimations"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "watchForFileChanges"
|
||||
},
|
||||
{
|
||||
"value": "**/*.cy.{js,jsx,ts,tsx}",
|
||||
"from": "default",
|
||||
"field": "specPattern"
|
||||
},
|
||||
{
|
||||
"value": [
|
||||
{
|
||||
"name": "chrome",
|
||||
"family": "chromium",
|
||||
"channel": "stable",
|
||||
"displayName": "Chrome",
|
||||
"version": "109.0.5414.119",
|
||||
"path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
"minSupportedVersion": 64,
|
||||
"majorVersion": "109"
|
||||
},
|
||||
{
|
||||
"name": "firefox",
|
||||
"family": "firefox",
|
||||
"channel": "stable",
|
||||
"displayName": "Firefox",
|
||||
"version": "107.0.1",
|
||||
"path": "/Applications/Firefox.app/Contents/MacOS/firefox",
|
||||
"minSupportedVersion": 86,
|
||||
"majorVersion": "107"
|
||||
},
|
||||
{
|
||||
"name": "electron",
|
||||
"channel": "stable",
|
||||
"family": "chromium",
|
||||
"displayName": "Electron",
|
||||
"version": "106.0.5249.51",
|
||||
"path": "",
|
||||
"majorVersion": 106
|
||||
}
|
||||
],
|
||||
"from": "runtime",
|
||||
"field": "browsers"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "hosts"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "isInteractive"
|
||||
}
|
||||
],
|
||||
"savedState": {
|
||||
"firstOpened": 1674605493218,
|
||||
"lastOpened": 1675053721981,
|
||||
"lastProjectId": "vgqrwp",
|
||||
"specFilter": ""
|
||||
},
|
||||
"configFile": "cypress.config.ts",
|
||||
"configFileAbsolutePath": "/Users/lachlanmiller/code/dump/eleutheria/packages/frontend/cypress.config.ts",
|
||||
"projectId": "vgqrwp",
|
||||
"branch": "main",
|
||||
"codeGenGlobs": {
|
||||
"id": "Q29kZUdlbkdsb2JzOioudnVl",
|
||||
"component": "*.vue",
|
||||
"__typename": "CodeGenGlobs"
|
||||
},
|
||||
"fileExtensionToUse": "ts",
|
||||
"defaultSpecFileName": "cypress/component/ComponentName.cy.tsx",
|
||||
"codeGenFramework": "vue",
|
||||
"isDefaultSpecPattern": true,
|
||||
"__typename": "CurrentProject"
|
||||
},
|
||||
"cloudViewer": {
|
||||
"id": "Q2xvdWRVc2VyOjcxYTM3NmVhLTdlMGUtNDBhOS1hMTAzLWMwM2NmNTMyMmQyZg==",
|
||||
"firstOrganization": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRPcmdhbml6YXRpb246NjE5ODJiMmItOTRmNy00ZjYzLTlmYjctNGI1MTc4NjQ5OWJh",
|
||||
"__typename": "CloudOrganization"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudOrganizationConnection"
|
||||
},
|
||||
"__typename": "CloudUser"
|
||||
},
|
||||
"cachedUser": {
|
||||
"id": "Q2FjaGVkVXNlcjpsYWNobGFuLm1pbGxlci4xOTkwQG91dGxvb2suY29t",
|
||||
"__typename": "CachedUser"
|
||||
},
|
||||
"localSettings": {
|
||||
"availableEditors": [
|
||||
{
|
||||
"id": "computer",
|
||||
"name": "Finder",
|
||||
"binary": "computer",
|
||||
"__typename": "Editor"
|
||||
},
|
||||
{
|
||||
"id": "code",
|
||||
"name": "Visual Studio Code",
|
||||
"binary": "code",
|
||||
"__typename": "Editor"
|
||||
},
|
||||
{
|
||||
"id": "vim",
|
||||
"name": "Vim",
|
||||
"binary": "vim",
|
||||
"__typename": "Editor"
|
||||
}
|
||||
],
|
||||
"preferences": {
|
||||
"preferredEditorBinary": null,
|
||||
"__typename": "LocalSettingsPreferences"
|
||||
},
|
||||
"__typename": "LocalSettings"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"currentProject": {
|
||||
"id": "debug-test-project-id",
|
||||
"branch": "main",
|
||||
"projectId": "vgqrwp",
|
||||
"__typename": "CurrentProject"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,419 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"cloudViewer": {
|
||||
"id": "Q2xvdWRVc2VyOjcxYTM3NmVhLTdlMGUtNDBhOS1hMTAzLWMwM2NmNTMyMmQyZg==",
|
||||
"fullName": "Lachlan Miller",
|
||||
"email": "lachlan.miller.1990@outlook.com",
|
||||
"firstOrganization": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRPcmdhbml6YXRpb246NjE5ODJiMmItOTRmNy00ZjYzLTlmYjctNGI1MTc4NjQ5OWJh",
|
||||
"__typename": "CloudOrganization"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudOrganizationConnection"
|
||||
},
|
||||
"__typename": "CloudUser"
|
||||
},
|
||||
"cachedUser": {
|
||||
"id": "Q2FjaGVkVXNlcjpsYWNobGFuLm1pbGxlci4xOTkwQG91dGxvb2suY29t",
|
||||
"fullName": "Lachlan Miller",
|
||||
"email": "lachlan.miller.1990@outlook.com",
|
||||
"__typename": "CachedUser"
|
||||
},
|
||||
"authState": {
|
||||
"name": null,
|
||||
"__typename": "AuthState"
|
||||
},
|
||||
"currentProject": {
|
||||
"id": "Q3VycmVudFByb2plY3Q6L1VzZXJzL2xhY2hsYW5taWxsZXIvY29kZS9kdW1wL2VsZXV0aGVyaWEvcGFja2FnZXMvZnJvbnRlbmQ=",
|
||||
"config": [
|
||||
{
|
||||
"value": 5,
|
||||
"from": "default",
|
||||
"field": "animationDistanceThreshold"
|
||||
},
|
||||
{
|
||||
"value": "arm64",
|
||||
"from": "default",
|
||||
"field": "arch"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "baseUrl"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "blockHosts"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "chromeWebSecurity"
|
||||
},
|
||||
{
|
||||
"value": [],
|
||||
"from": "default",
|
||||
"field": "clientCertificates"
|
||||
},
|
||||
{
|
||||
"value": 4000,
|
||||
"from": "default",
|
||||
"field": "defaultCommandTimeout"
|
||||
},
|
||||
{
|
||||
"value": "cypress/downloads",
|
||||
"from": "default",
|
||||
"field": "downloadsFolder"
|
||||
},
|
||||
{
|
||||
"value": {
|
||||
"INTERNAL_CLOUD_ENV": "production",
|
||||
"INTERNAL_GRAPHQL_PORT": 4444,
|
||||
"INTERNAL_EVENT_COLLECTOR_ENV": "staging",
|
||||
"CONFIG_ENV": "production"
|
||||
},
|
||||
"field": "env",
|
||||
"from": "env"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "execTimeout"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalFetchPolyfill"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalInteractiveRunEvents"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalRunAllSpecs"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalMemoryManagement"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalModifyObstructiveThirdPartyCode"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "experimentalSkipDomainInjection"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalOriginDependencies"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalSourceRewriting"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "config",
|
||||
"field": "experimentalSingleTabRunMode"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalStudio"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalWebKitSupport"
|
||||
},
|
||||
{
|
||||
"value": "",
|
||||
"from": "default",
|
||||
"field": "fileServerFolder"
|
||||
},
|
||||
{
|
||||
"value": "cypress/fixtures",
|
||||
"from": "default",
|
||||
"field": "fixturesFolder"
|
||||
},
|
||||
{
|
||||
"value": [
|
||||
"**/__snapshots__/*",
|
||||
"**/__image_snapshots__/*"
|
||||
],
|
||||
"from": "default",
|
||||
"field": "excludeSpecPattern"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "includeShadowDom"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"from": "default",
|
||||
"field": "keystrokeDelay"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "modifyObstructiveCode"
|
||||
},
|
||||
{
|
||||
"from": "default",
|
||||
"field": "nodeVersion"
|
||||
},
|
||||
{
|
||||
"value": 50,
|
||||
"from": "default",
|
||||
"field": "numTestsKeptInMemory"
|
||||
},
|
||||
{
|
||||
"value": "darwin",
|
||||
"from": "default",
|
||||
"field": "platform"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "pageLoadTimeout"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "port"
|
||||
},
|
||||
{
|
||||
"value": "7p5uce",
|
||||
"from": "config",
|
||||
"field": "projectId"
|
||||
},
|
||||
{
|
||||
"value": 20,
|
||||
"from": "default",
|
||||
"field": "redirectionLimit"
|
||||
},
|
||||
{
|
||||
"value": "spec",
|
||||
"from": "default",
|
||||
"field": "reporter"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "reporterOptions"
|
||||
},
|
||||
{
|
||||
"value": 5000,
|
||||
"from": "default",
|
||||
"field": "requestTimeout"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "resolvedNodePath"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "resolvedNodeVersion"
|
||||
},
|
||||
{
|
||||
"value": 30000,
|
||||
"from": "default",
|
||||
"field": "responseTimeout"
|
||||
},
|
||||
{
|
||||
"value": {
|
||||
"runMode": 0,
|
||||
"openMode": 0
|
||||
},
|
||||
"from": "default",
|
||||
"field": "retries"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "screenshotOnRunFailure"
|
||||
},
|
||||
{
|
||||
"value": "cypress/screenshots",
|
||||
"from": "default",
|
||||
"field": "screenshotsFolder"
|
||||
},
|
||||
{
|
||||
"value": 250,
|
||||
"from": "default",
|
||||
"field": "slowTestThreshold"
|
||||
},
|
||||
{
|
||||
"value": "top",
|
||||
"from": "default",
|
||||
"field": "scrollBehavior"
|
||||
},
|
||||
{
|
||||
"value": "cypress/support/component.{js,jsx,ts,tsx}",
|
||||
"from": "default",
|
||||
"field": "supportFile"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "supportFolder"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "taskTimeout"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "testIsolation"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "trashAssetsBeforeRuns"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "userAgent"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "video"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "videoCompression"
|
||||
},
|
||||
{
|
||||
"value": "cypress/videos",
|
||||
"from": "default",
|
||||
"field": "videosFolder"
|
||||
},
|
||||
{
|
||||
"value": 500,
|
||||
"from": "default",
|
||||
"field": "viewportHeight"
|
||||
},
|
||||
{
|
||||
"value": 500,
|
||||
"from": "default",
|
||||
"field": "viewportWidth"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "waitForAnimations"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "watchForFileChanges"
|
||||
},
|
||||
{
|
||||
"value": "**/*.cy.{js,jsx,ts,tsx}",
|
||||
"from": "default",
|
||||
"field": "specPattern"
|
||||
},
|
||||
{
|
||||
"value": [
|
||||
{
|
||||
"name": "chrome",
|
||||
"family": "chromium",
|
||||
"channel": "stable",
|
||||
"displayName": "Chrome",
|
||||
"version": "109.0.5414.119",
|
||||
"path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
"minSupportedVersion": 64,
|
||||
"majorVersion": "109"
|
||||
},
|
||||
{
|
||||
"name": "firefox",
|
||||
"family": "firefox",
|
||||
"channel": "stable",
|
||||
"displayName": "Firefox",
|
||||
"version": "107.0.1",
|
||||
"path": "/Applications/Firefox.app/Contents/MacOS/firefox",
|
||||
"minSupportedVersion": 86,
|
||||
"majorVersion": "107"
|
||||
},
|
||||
{
|
||||
"name": "electron",
|
||||
"channel": "stable",
|
||||
"family": "chromium",
|
||||
"displayName": "Electron",
|
||||
"version": "106.0.5249.51",
|
||||
"path": "",
|
||||
"majorVersion": 106
|
||||
}
|
||||
],
|
||||
"from": "runtime",
|
||||
"field": "browsers"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "hosts"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "isInteractive"
|
||||
}
|
||||
],
|
||||
"isFullConfigReady": true,
|
||||
"hasNonExampleSpec": true,
|
||||
"savedState": {
|
||||
"firstOpened": 1674605493218,
|
||||
"lastOpened": 1675067256771,
|
||||
"lastProjectId": "7p5uce",
|
||||
"specFilter": "",
|
||||
"banners": {
|
||||
"aci_082022_record": {
|
||||
"lastShown": 1675061062102
|
||||
}
|
||||
}
|
||||
},
|
||||
"cloudProject": {
|
||||
"__typename": "CloudProject",
|
||||
"id": "Q2xvdWRQcm9qZWN0OjdwNXVjZQ==",
|
||||
"runs": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRSdW46bkdudmx5d3BHWg==",
|
||||
"status": "PASSED",
|
||||
"url": "https://cloud.cypress.io/projects/7p5uce/runs/2",
|
||||
"__typename": "CloudRun"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRSdW46YkxtdnhXWmpPUA==",
|
||||
"status": "FAILED",
|
||||
"url": "https://cloud.cypress.io/projects/7p5uce/runs/1",
|
||||
"__typename": "CloudRun"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudRunConnection"
|
||||
}
|
||||
},
|
||||
"__typename": "CurrentProject"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,656 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"currentProject": {
|
||||
"id": "Q3VycmVudFByb2plY3Q6L1VzZXJzL2xhY2hsYW5taWxsZXIvY29kZS9kdW1wL2VsZXV0aGVyaWEvcGFja2FnZXMvZnJvbnRlbmQ=",
|
||||
"title": "frontend",
|
||||
"config": [
|
||||
{
|
||||
"value": 5,
|
||||
"from": "default",
|
||||
"field": "animationDistanceThreshold"
|
||||
},
|
||||
{
|
||||
"value": "arm64",
|
||||
"from": "default",
|
||||
"field": "arch"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "baseUrl"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "blockHosts"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "chromeWebSecurity"
|
||||
},
|
||||
{
|
||||
"value": [],
|
||||
"from": "default",
|
||||
"field": "clientCertificates"
|
||||
},
|
||||
{
|
||||
"value": 4000,
|
||||
"from": "default",
|
||||
"field": "defaultCommandTimeout"
|
||||
},
|
||||
{
|
||||
"value": "cypress/downloads",
|
||||
"from": "default",
|
||||
"field": "downloadsFolder"
|
||||
},
|
||||
{
|
||||
"value": {
|
||||
"INTERNAL_CLOUD_ENV": "production",
|
||||
"INTERNAL_GRAPHQL_PORT": 4444,
|
||||
"INTERNAL_EVENT_COLLECTOR_ENV": "staging",
|
||||
"CONFIG_ENV": "production"
|
||||
},
|
||||
"field": "env",
|
||||
"from": "env"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "execTimeout"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalFetchPolyfill"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalInteractiveRunEvents"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalRunAllSpecs"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalMemoryManagement"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalModifyObstructiveThirdPartyCode"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "experimentalSkipDomainInjection"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalOriginDependencies"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalSourceRewriting"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "config",
|
||||
"field": "experimentalSingleTabRunMode"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalStudio"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalWebKitSupport"
|
||||
},
|
||||
{
|
||||
"value": "",
|
||||
"from": "default",
|
||||
"field": "fileServerFolder"
|
||||
},
|
||||
{
|
||||
"value": "cypress/fixtures",
|
||||
"from": "default",
|
||||
"field": "fixturesFolder"
|
||||
},
|
||||
{
|
||||
"value": [
|
||||
"**/__snapshots__/*",
|
||||
"**/__image_snapshots__/*"
|
||||
],
|
||||
"from": "default",
|
||||
"field": "excludeSpecPattern"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "includeShadowDom"
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"from": "default",
|
||||
"field": "keystrokeDelay"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "modifyObstructiveCode"
|
||||
},
|
||||
{
|
||||
"from": "default",
|
||||
"field": "nodeVersion"
|
||||
},
|
||||
{
|
||||
"value": 50,
|
||||
"from": "default",
|
||||
"field": "numTestsKeptInMemory"
|
||||
},
|
||||
{
|
||||
"value": "darwin",
|
||||
"from": "default",
|
||||
"field": "platform"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "pageLoadTimeout"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "port"
|
||||
},
|
||||
{
|
||||
"value": "vgqrwp",
|
||||
"from": "config",
|
||||
"field": "projectId"
|
||||
},
|
||||
{
|
||||
"value": 20,
|
||||
"from": "default",
|
||||
"field": "redirectionLimit"
|
||||
},
|
||||
{
|
||||
"value": "spec",
|
||||
"from": "default",
|
||||
"field": "reporter"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "reporterOptions"
|
||||
},
|
||||
{
|
||||
"value": 5000,
|
||||
"from": "default",
|
||||
"field": "requestTimeout"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "resolvedNodePath"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "resolvedNodeVersion"
|
||||
},
|
||||
{
|
||||
"value": 30000,
|
||||
"from": "default",
|
||||
"field": "responseTimeout"
|
||||
},
|
||||
{
|
||||
"value": {
|
||||
"runMode": 0,
|
||||
"openMode": 0
|
||||
},
|
||||
"from": "default",
|
||||
"field": "retries"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "screenshotOnRunFailure"
|
||||
},
|
||||
{
|
||||
"value": "cypress/screenshots",
|
||||
"from": "default",
|
||||
"field": "screenshotsFolder"
|
||||
},
|
||||
{
|
||||
"value": 250,
|
||||
"from": "default",
|
||||
"field": "slowTestThreshold"
|
||||
},
|
||||
{
|
||||
"value": "top",
|
||||
"from": "default",
|
||||
"field": "scrollBehavior"
|
||||
},
|
||||
{
|
||||
"value": "cypress/support/component.{js,jsx,ts,tsx}",
|
||||
"from": "default",
|
||||
"field": "supportFile"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "supportFolder"
|
||||
},
|
||||
{
|
||||
"value": 60000,
|
||||
"from": "default",
|
||||
"field": "taskTimeout"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "testIsolation"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "trashAssetsBeforeRuns"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "userAgent"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "video"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "videoCompression"
|
||||
},
|
||||
{
|
||||
"value": "cypress/videos",
|
||||
"from": "default",
|
||||
"field": "videosFolder"
|
||||
},
|
||||
{
|
||||
"value": 500,
|
||||
"from": "default",
|
||||
"field": "viewportHeight"
|
||||
},
|
||||
{
|
||||
"value": 500,
|
||||
"from": "default",
|
||||
"field": "viewportWidth"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "waitForAnimations"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "watchForFileChanges"
|
||||
},
|
||||
{
|
||||
"value": "**/*.cy.{js,jsx,ts,tsx}",
|
||||
"from": "default",
|
||||
"field": "specPattern"
|
||||
},
|
||||
{
|
||||
"value": [
|
||||
{
|
||||
"name": "chrome",
|
||||
"family": "chromium",
|
||||
"channel": "stable",
|
||||
"displayName": "Chrome",
|
||||
"version": "109.0.5414.119",
|
||||
"path": "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||
"minSupportedVersion": 64,
|
||||
"majorVersion": "109"
|
||||
},
|
||||
{
|
||||
"name": "firefox",
|
||||
"family": "firefox",
|
||||
"channel": "stable",
|
||||
"displayName": "Firefox",
|
||||
"version": "107.0.1",
|
||||
"path": "/Applications/Firefox.app/Contents/MacOS/firefox",
|
||||
"minSupportedVersion": 86,
|
||||
"majorVersion": "107"
|
||||
},
|
||||
{
|
||||
"name": "electron",
|
||||
"channel": "stable",
|
||||
"family": "chromium",
|
||||
"displayName": "Electron",
|
||||
"version": "106.0.5249.51",
|
||||
"path": "",
|
||||
"majorVersion": 106
|
||||
}
|
||||
],
|
||||
"from": "runtime",
|
||||
"field": "browsers"
|
||||
},
|
||||
{
|
||||
"value": null,
|
||||
"from": "default",
|
||||
"field": "hosts"
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"from": "default",
|
||||
"field": "isInteractive"
|
||||
}
|
||||
],
|
||||
"savedState": {
|
||||
"firstOpened": 1674605493218,
|
||||
"lastOpened": 1675053721981,
|
||||
"lastProjectId": "vgqrwp",
|
||||
"specFilter": ""
|
||||
},
|
||||
"currentTestingType": "component",
|
||||
"branch": "main",
|
||||
"packageManager": "yarn",
|
||||
"activeBrowser": {
|
||||
"id": "QnJvd3NlcjpjaHJvbWUtY2hyb21pdW0tc3RhYmxl",
|
||||
"displayName": "Chrome",
|
||||
"majorVersion": "109",
|
||||
"__typename": "Browser"
|
||||
},
|
||||
"browsers": [
|
||||
{
|
||||
"id": "QnJvd3NlcjpjaHJvbWUtY2hyb21pdW0tc3RhYmxl",
|
||||
"isSelected": true,
|
||||
"displayName": "Chrome",
|
||||
"version": "109.0.5414.119",
|
||||
"majorVersion": "109",
|
||||
"isVersionSupported": true,
|
||||
"warning": null,
|
||||
"disabled": null,
|
||||
"__typename": "Browser"
|
||||
},
|
||||
{
|
||||
"id": "QnJvd3NlcjpmaXJlZm94LWZpcmVmb3gtc3RhYmxl",
|
||||
"isSelected": false,
|
||||
"displayName": "Firefox",
|
||||
"version": "107.0.1",
|
||||
"majorVersion": "107",
|
||||
"isVersionSupported": true,
|
||||
"warning": null,
|
||||
"disabled": null,
|
||||
"__typename": "Browser"
|
||||
},
|
||||
{
|
||||
"id": "QnJvd3NlcjplbGVjdHJvbi1jaHJvbWl1bS1zdGFibGU=",
|
||||
"isSelected": false,
|
||||
"displayName": "Electron",
|
||||
"version": "106.0.5249.51",
|
||||
"majorVersion": "106",
|
||||
"isVersionSupported": true,
|
||||
"warning": null,
|
||||
"disabled": null,
|
||||
"__typename": "Browser"
|
||||
}
|
||||
],
|
||||
"projectId": "vgqrwp",
|
||||
"cloudProject": {
|
||||
"__typename": "CloudProject",
|
||||
"id": "Q2xvdWRQcm9qZWN0OnZncXJ3cA=="
|
||||
},
|
||||
"__typename": "CurrentProject"
|
||||
},
|
||||
"isGlobalMode": true,
|
||||
"versions": {
|
||||
"current": {
|
||||
"id": "12.4.0",
|
||||
"version": "12.4.0",
|
||||
"released": "2023-01-24T18:40:53.125Z",
|
||||
"__typename": "Version"
|
||||
},
|
||||
"latest": {
|
||||
"id": "12.4.1",
|
||||
"version": "12.4.1",
|
||||
"released": "2023-01-27T15:00:32.366Z",
|
||||
"__typename": "Version"
|
||||
},
|
||||
"__typename": "VersionData"
|
||||
},
|
||||
"cloudViewer": {
|
||||
"id": "Q2xvdWRVc2VyOjcxYTM3NmVhLTdlMGUtNDBhOS1hMTAzLWMwM2NmNTMyMmQyZg==",
|
||||
"cloudOrganizationsUrl": "https://cloud.cypress.io/organizations",
|
||||
"organizations": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRPcmdhbml6YXRpb246NjE5ODJiMmItOTRmNy00ZjYzLTlmYjctNGI1MTc4NjQ5OWJh",
|
||||
"name": "Org 2",
|
||||
"projects": {
|
||||
"nodes": [],
|
||||
"__typename": "CloudProjectConnection"
|
||||
},
|
||||
"__typename": "CloudOrganization"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRPcmdhbml6YXRpb246MDIxZmVhNjctZDYwOC00YWIyLWFmMTctM2Y4YTJhMjNkMDE5",
|
||||
"name": "Lachlan's Personal Projects",
|
||||
"projects": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OnZncXJ3cA==",
|
||||
"slug": "vgqrwp",
|
||||
"name": "Rhythm Game",
|
||||
"__typename": "CloudProject"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudProjectConnection"
|
||||
},
|
||||
"__typename": "CloudOrganization"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRPcmdhbml6YXRpb246ODllYmMwOTktNzhjMS00YjIzLWIwYzMtNjAzMGY0MjAxNDBj",
|
||||
"name": "Lachlan Miller",
|
||||
"projects": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Om9mODhoNQ==",
|
||||
"slug": "of88h5",
|
||||
"name": "baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Onp5N2dzZQ==",
|
||||
"slug": "zy7gse",
|
||||
"name": "express",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmZ1aDkzOQ==",
|
||||
"slug": "fuh939",
|
||||
"name": "bannerjs",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OjVicHF0MQ==",
|
||||
"slug": "5bpqt1",
|
||||
"name": "baretest88",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OjJ5dm1odQ==",
|
||||
"slug": "2yvmhu",
|
||||
"name": "baretest414141",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Ojk4dzhveQ==",
|
||||
"slug": "98w8oy",
|
||||
"name": "desktop-gui-testing",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmJqdWJjYQ==",
|
||||
"slug": "bjubca",
|
||||
"name": "baretest58",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmQ4ZjM5bQ==",
|
||||
"slug": "d8f39m",
|
||||
"name": "baretest00",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmR3am5vMg==",
|
||||
"slug": "dwjno2",
|
||||
"name": "baretest66",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmZ3ZHZ1Mw==",
|
||||
"slug": "fwdvu3",
|
||||
"name": "31baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OnVxNHhyYg==",
|
||||
"slug": "uq4xrb",
|
||||
"name": "baretest33331",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Ong5Y3BzOQ==",
|
||||
"slug": "x9cps9",
|
||||
"name": "555baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmZ6bW53Yw==",
|
||||
"slug": "fzmnwc",
|
||||
"name": "baretestdd",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OnU5Y3d2Zg==",
|
||||
"slug": "u9cwvf",
|
||||
"name": "baretest-41",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Om9rZDQ3OA==",
|
||||
"slug": "okd478",
|
||||
"name": "baretest-1231",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OjkxNTZiMw==",
|
||||
"slug": "9156b3",
|
||||
"name": "baretest555",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmlvbmNhbg==",
|
||||
"slug": "ioncan",
|
||||
"name": "baretest-asdf",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OnpuYm9qOQ==",
|
||||
"slug": "znboj9",
|
||||
"name": "baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmljczdteA==",
|
||||
"slug": "ics7mx",
|
||||
"name": "baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OnN1cjRidw==",
|
||||
"slug": "sur4bw",
|
||||
"name": "baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OjF1b2c1eA==",
|
||||
"slug": "1uog5x",
|
||||
"name": "baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Om52MXJ0OA==",
|
||||
"slug": "nv1rt8",
|
||||
"name": "baretest",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OmlnM2Nzaw==",
|
||||
"slug": "ig3csk",
|
||||
"name": "baretest-1",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OjhlbWU2MQ==",
|
||||
"slug": "8eme61",
|
||||
"name": "rhythm-frontendddd",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0Ojk4anA1Ng==",
|
||||
"slug": "98jp56",
|
||||
"name": "rhythm-frontend",
|
||||
"__typename": "CloudProject"
|
||||
},
|
||||
{
|
||||
"id": "Q2xvdWRQcm9qZWN0OjNlNWJwYg==",
|
||||
"slug": "3e5bpb",
|
||||
"name": "Lachlan Miller Testing",
|
||||
"__typename": "CloudProject"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudProjectConnection"
|
||||
},
|
||||
"__typename": "CloudOrganization"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudOrganizationConnection"
|
||||
},
|
||||
"email": "lachlan.miller.1990@outlook.com",
|
||||
"fullName": "Lachlan Miller",
|
||||
"firstOrganization": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "Q2xvdWRPcmdhbml6YXRpb246NjE5ODJiMmItOTRmNy00ZjYzLTlmYjctNGI1MTc4NjQ5OWJh",
|
||||
"__typename": "CloudOrganization"
|
||||
}
|
||||
],
|
||||
"__typename": "CloudOrganizationConnection"
|
||||
},
|
||||
"__typename": "CloudUser"
|
||||
},
|
||||
"authState": {
|
||||
"browserOpened": false,
|
||||
"name": null,
|
||||
"message": null,
|
||||
"__typename": "AuthState"
|
||||
},
|
||||
"cachedUser": {
|
||||
"id": "Q2FjaGVkVXNlcjpsYWNobGFuLm1pbGxlci4xOTkwQG91dGxvb2suY29t",
|
||||
"fullName": "Lachlan Miller",
|
||||
"email": "lachlan.miller.1990@outlook.com",
|
||||
"__typename": "CachedUser"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"baseError": null,
|
||||
"currentProject": {
|
||||
"id": "Q3VycmVudFByb2plY3Q6L1VzZXJzL2xhY2hsYW5taWxsZXIvY29kZS9kdW1wL2VsZXV0aGVyaWEvcGFja2FnZXMvZnJvbnRlbmQ=",
|
||||
"isLoadingConfigFile": false,
|
||||
"isLoadingNodeEvents": false,
|
||||
"__typename": "CurrentProject"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"localSettings": {
|
||||
"preferences": {
|
||||
"isSideNavigationOpen": true,
|
||||
"isSpecsListOpen": false,
|
||||
"autoScrollingEnabled": false,
|
||||
"reporterWidth": 618,
|
||||
"specListWidth": null,
|
||||
"__typename": "LocalSettingsPreferences"
|
||||
},
|
||||
"__typename": "LocalSettings"
|
||||
},
|
||||
"currentProject": {
|
||||
"id": "Q3VycmVudFByb2plY3Q6L1VzZXJzL2xhY2hsYW5taWxsZXIvY29kZS9kdW1wL2VsZXV0aGVyaWEvcGFja2FnZXMvZnJvbnRlbmQ=",
|
||||
"cloudProject": {
|
||||
"__typename": "CloudProject",
|
||||
"id": "Q2xvdWRQcm9qZWN0OjdwNXVjZQ==",
|
||||
"runByNumber": {
|
||||
"id": "Q2xvdWRSdW46bkdudmx5d3BHWg==",
|
||||
"status": "PASSED",
|
||||
"totalFailed": 0,
|
||||
"__typename": "CloudRun"
|
||||
}
|
||||
},
|
||||
"isCTConfigured": true,
|
||||
"isE2EConfigured": true,
|
||||
"currentTestingType": "component",
|
||||
"title": "frontend",
|
||||
"branch": "main",
|
||||
"__typename": "CurrentProject"
|
||||
},
|
||||
"invokedFromCli": true
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"data": {
|
||||
"currentProject": {
|
||||
"id": "Q3VycmVudFByb2plY3Q6L1VzZXJzL2xhY2hsYW5taWxsZXIvY29kZS9kdW1wL2VsZXV0aGVyaWEvcGFja2FnZXMvZnJvbnRlbmQ=",
|
||||
"branch": "main",
|
||||
"projectId": "7p5uce",
|
||||
"__typename": "CurrentProject"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,6 @@ type NonNullCloudSpec = Exclude<SpecsListFragment['cloudSpec'], undefined | null
|
||||
export function useCloudSpecData (
|
||||
isProjectDisconnected: Ref<boolean>,
|
||||
isOffline: Ref<boolean>,
|
||||
projectId: string | null | undefined,
|
||||
mostRecentUpdate: Ref<string | null>,
|
||||
displayedSpecs: Ref<(SpecsListFragment | undefined)[]>,
|
||||
allSpecs: (SpecsListFragment | undefined)[],
|
||||
|
||||
@@ -13,9 +13,15 @@ import { uniq } from 'lodash'
|
||||
* subscriptions had ended when the component it was registered in was unmounted.
|
||||
*/
|
||||
gql`
|
||||
|
||||
fragment UseRelevantRun on RelevantRun {
|
||||
all {
|
||||
runId
|
||||
runNumber
|
||||
sha
|
||||
status
|
||||
}
|
||||
latest {
|
||||
runId
|
||||
runNumber
|
||||
sha
|
||||
status
|
||||
@@ -42,7 +48,7 @@ gql`
|
||||
|
||||
`
|
||||
|
||||
export function useRelevantRun (location: 'SIDEBAR' | 'DEBUG') {
|
||||
export function useRelevantRun (location: 'SIDEBAR' | 'DEBUG' | 'RUNS' | 'SPECS') {
|
||||
const userProjectStatusStore = useUserProjectStatusStore()
|
||||
|
||||
const shouldPause = computed(() => {
|
||||
@@ -68,6 +74,7 @@ export function useRelevantRun (location: 'SIDEBAR' | 'DEBUG') {
|
||||
|
||||
return {
|
||||
all: subscriptionResponse.data.value?.relevantRuns?.all,
|
||||
latest: subscriptionResponse.data.value?.relevantRuns?.latest,
|
||||
commitsAhead: subscriptionResponse.data.value?.relevantRuns?.commitsAhead,
|
||||
selectedRun,
|
||||
commitShas,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<RunsContainer
|
||||
v-else
|
||||
:gql="query.data.value"
|
||||
:runs="runs"
|
||||
:online="isOnlineRef"
|
||||
data-cy="runs-container"
|
||||
@re-execute-runs-query="reExecuteRunsQuery"
|
||||
@@ -14,24 +15,31 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watchEffect } from 'vue'
|
||||
import { gql, useQuery } from '@urql/vue'
|
||||
import { RunsDocument } from '../generated/graphql'
|
||||
import { Ref, ref, watchEffect } from 'vue'
|
||||
import RunsSkeleton from '../runs/RunsSkeleton.vue'
|
||||
import RunsContainer from '../runs/RunsContainer.vue'
|
||||
import TransitionQuickFade from '@cy/components/transitions/TransitionQuickFade.vue'
|
||||
import { useUserProjectStatusStore } from '@packages/frontend-shared/src/store/user-project-status-store'
|
||||
import { useOnline } from '@vueuse/core'
|
||||
|
||||
gql`
|
||||
query Runs {
|
||||
...RunsContainer
|
||||
}`
|
||||
|
||||
const query = useQuery({ query: RunsDocument, requestPolicy: 'network-only' })
|
||||
import { useProjectRuns } from '../runs/useProjectRuns'
|
||||
import { useGitTreeRuns } from '../runs/useGitTreeRuns'
|
||||
import type { RunsComposable } from '../runs/RunsComposable'
|
||||
|
||||
const isOnlineRef = ref(true)
|
||||
const online = useOnline()
|
||||
|
||||
const isUsingGit = useUserProjectStatusStore().project.isUsingGit
|
||||
|
||||
let runComposable: (online: Ref<boolean>) => RunsComposable
|
||||
|
||||
if (isUsingGit) {
|
||||
runComposable = useGitTreeRuns
|
||||
} else {
|
||||
runComposable = useProjectRuns
|
||||
}
|
||||
|
||||
const { runs, reExecuteRunsQuery, query } = runComposable(isOnlineRef)
|
||||
|
||||
watchEffect(() => {
|
||||
// We want to keep track of the previous state to refetch the query
|
||||
// when the internet connection is back
|
||||
@@ -41,11 +49,8 @@ watchEffect(() => {
|
||||
|
||||
if (online.value && !isOnlineRef.value) {
|
||||
isOnlineRef.value = true
|
||||
query.executeQuery()
|
||||
reExecuteRunsQuery()
|
||||
}
|
||||
})
|
||||
|
||||
function reExecuteRunsQuery () {
|
||||
query.executeQuery()
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -21,18 +21,27 @@
|
||||
:is-default-spec-pattern="isDefaultSpecPattern"
|
||||
@showCreateSpecModal="showCreateSpecModal"
|
||||
/>
|
||||
<SpecsListRunWatcher
|
||||
v-for="run in latestRuns"
|
||||
:key="run.runId"
|
||||
:run="run"
|
||||
@run-update="runUpdate()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { gql, SubscriptionHandlerArg, useQuery } from '@urql/vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { gql, useQuery } from '@urql/vue'
|
||||
import { useI18n } from '@cy/i18n'
|
||||
import SpecsList from '../../specs/SpecsList.vue'
|
||||
import NoSpecsPage from '../../specs/NoSpecsPage.vue'
|
||||
import CreateSpecModal from '../../specs/CreateSpecModal.vue'
|
||||
import { SpecsPageContainerDocument, SpecsPageContainer_SpecsChangeDocument, SpecsPageContainer_SpecListPollingDocument, SpecsPageContainer_BranchInfoDocument } from '../../generated/graphql'
|
||||
import SpecsListRunWatcher from '../../specs/SpecsListRunWatcher.vue'
|
||||
import { SpecsPageContainerDocument, SpecsPageContainer_SpecsChangeDocument } from '../../generated/graphql'
|
||||
import { useSubscription } from '../../graphql'
|
||||
import { useRelevantRun } from '../../composables/useRelevantRun'
|
||||
import { isEmpty } from 'lodash'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -47,7 +56,7 @@ query SpecsPageContainer_BranchInfo {
|
||||
`
|
||||
|
||||
gql`
|
||||
query SpecsPageContainer($fromBranch: String!, $hasBranch: Boolean!) {
|
||||
query SpecsPageContainer($runIds: [ID!]!, $hasRunIds: Boolean!) {
|
||||
...Specs_SpecsList
|
||||
...NoSpecsPage
|
||||
...CreateSpecModal
|
||||
@@ -59,37 +68,24 @@ query SpecsPageContainer($fromBranch: String!, $hasBranch: Boolean!) {
|
||||
`
|
||||
|
||||
gql`
|
||||
subscription SpecsPageContainer_specsChange($fromBranch: String!, $hasBranch: Boolean!) {
|
||||
subscription SpecsPageContainer_specsChange($runIds: [ID!]!, $hasRunIds: Boolean!) {
|
||||
specsChange {
|
||||
id
|
||||
specs {
|
||||
id
|
||||
...SpecsList
|
||||
...SpecsList
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
gql`
|
||||
subscription SpecsPageContainer_specListPolling($fromBranch: String, $projectId: String) {
|
||||
startPollingForSpecs(branchName: $fromBranch, projectId: $projectId)
|
||||
}
|
||||
`
|
||||
|
||||
const branchInfo = useQuery({ query: SpecsPageContainer_BranchInfoDocument })
|
||||
const relevantRuns = useRelevantRun('SPECS')
|
||||
|
||||
const variables = computed(() => {
|
||||
const fromBranch = branchInfo.data.value?.currentProject?.branch ?? ''
|
||||
const hasBranch = Boolean(fromBranch)
|
||||
const runIds = relevantRuns.value.latest?.map((run) => run.runId) || []
|
||||
const hasRunIds = !isEmpty(runIds)
|
||||
|
||||
return { fromBranch, hasBranch }
|
||||
})
|
||||
|
||||
const pollingVariables = computed(() => {
|
||||
const fromBranch = branchInfo.data.value?.currentProject?.branch ?? null
|
||||
const projectId = branchInfo.data.value?.currentProject?.projectId ?? null
|
||||
|
||||
return { fromBranch, projectId }
|
||||
return { runIds, hasRunIds }
|
||||
})
|
||||
|
||||
useSubscription({
|
||||
@@ -97,17 +93,29 @@ useSubscription({
|
||||
variables,
|
||||
})
|
||||
|
||||
const mostRecentUpdate = ref<string|null>(null)
|
||||
/**
|
||||
* Used to trigger Spec updates via the useCloudSpec composable.
|
||||
*/
|
||||
const mostRecentUpdate = ref<string | undefined>()
|
||||
|
||||
const updateMostRecentUpdate: SubscriptionHandlerArg<any, any> = (_, reportedUpdate) => {
|
||||
mostRecentUpdate.value = reportedUpdate?.startPollingForSpecs ?? null
|
||||
/**
|
||||
* At this time, the CloudRun is not passing the `updatedAt` field. To mimic
|
||||
* that, we are setting the current date/time here each time any of the runs change.
|
||||
*/
|
||||
watch(() => relevantRuns.value, (value, oldValue) => {
|
||||
if (value && oldValue && value.all !== oldValue.all) {
|
||||
runUpdate()
|
||||
}
|
||||
})
|
||||
|
||||
const latestRuns = computed(() => {
|
||||
return relevantRuns.value.latest
|
||||
})
|
||||
|
||||
const runUpdate = () => {
|
||||
mostRecentUpdate.value = new Date().toISOString()
|
||||
}
|
||||
|
||||
useSubscription({
|
||||
query: SpecsPageContainer_SpecListPollingDocument,
|
||||
variables: pollingVariables,
|
||||
}, updateMostRecentUpdate)
|
||||
|
||||
const query = useQuery({
|
||||
query: SpecsPageContainerDocument,
|
||||
variables,
|
||||
|
||||
@@ -751,7 +751,13 @@ export class EventManager {
|
||||
* Return it's response.
|
||||
*/
|
||||
Cypress.primaryOriginCommunicator.on('backend:request', async ({ args }, { source, responseEvent }) => {
|
||||
const response = await Cypress.backend(...args)
|
||||
let response
|
||||
|
||||
try {
|
||||
response = await Cypress.backend(...args)
|
||||
} catch (error) {
|
||||
response = { error }
|
||||
}
|
||||
|
||||
Cypress.primaryOriginCommunicator.toSource(source, responseEvent, response)
|
||||
})
|
||||
|
||||
@@ -152,13 +152,25 @@ function setupRunner () {
|
||||
createIframeModel()
|
||||
}
|
||||
|
||||
interface GetSpecUrlOptions {
|
||||
browserFamily?: string
|
||||
namespace: string
|
||||
specSrc: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for the spec. This is the URL of the AUT IFrame.
|
||||
* CT uses absolute URLs, and serves from the dev server.
|
||||
* E2E uses relative, serving from our internal server's spec controller.
|
||||
*/
|
||||
function getSpecUrl (namespace: string, specSrc: string) {
|
||||
return `/${namespace}/iframes/${specSrc}`
|
||||
function getSpecUrl ({ browserFamily, namespace, specSrc }: GetSpecUrlOptions) {
|
||||
let url = `/${namespace}/iframes/${specSrc}`
|
||||
|
||||
if (browserFamily) {
|
||||
url += `?browserFamily=${browserFamily}`
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -202,13 +214,15 @@ export function addCrossOriginIframe (location) {
|
||||
return
|
||||
}
|
||||
|
||||
const config = getRunnerConfigFromWindow()
|
||||
|
||||
addIframe({
|
||||
id,
|
||||
// the cross origin iframe is added to the document body instead of the
|
||||
// container since it needs to match the size of the top window for screenshots
|
||||
$container: document.body,
|
||||
className: 'spec-bridge-iframe',
|
||||
src: `${location.origin}/${getRunnerConfigFromWindow().namespace}/spec-bridge-iframes`,
|
||||
src: `${location.origin}/${config.namespace}/spec-bridge-iframes?browserFamily=${config.browser.family}`,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -234,7 +248,10 @@ function runSpecCT (config, spec: SpecFile) {
|
||||
const autIframe = getAutIframeModel()
|
||||
const $autIframe: JQuery<HTMLIFrameElement> = autIframe.create().appendTo($container)
|
||||
|
||||
const specSrc = getSpecUrl(config.namespace, spec.absolute)
|
||||
const specSrc = getSpecUrl({
|
||||
namespace: config.namespace,
|
||||
specSrc: spec.absolute,
|
||||
})
|
||||
|
||||
autIframe._showInitialBlankPage()
|
||||
$autIframe.prop('src', specSrc)
|
||||
@@ -297,7 +314,11 @@ function runSpecE2E (config, spec: SpecFile) {
|
||||
autIframe.visitBlankPage()
|
||||
|
||||
// create Spec IFrame
|
||||
const specSrc = getSpecUrl(config.namespace, encodeURIComponent(spec.relative))
|
||||
const specSrc = getSpecUrl({
|
||||
browserFamily: config.browser.family,
|
||||
namespace: config.namespace,
|
||||
specSrc: encodeURIComponent(spec.relative),
|
||||
})
|
||||
|
||||
// FIXME: BILL Determine where to call client with to force browser repaint
|
||||
/**
|
||||
|
||||
@@ -70,9 +70,9 @@
|
||||
import { computed } from 'vue'
|
||||
import ListRowHeader from '@cy/components/ListRowHeader.vue'
|
||||
import ExternalLink from '@cy/gql-components/ExternalLink.vue'
|
||||
import { gql } from '@urql/core'
|
||||
import { gql, useSubscription } from '@urql/vue'
|
||||
import RunResults from './RunResults.vue'
|
||||
import type { CloudRunStatus, RunCardFragment } from '../generated/graphql'
|
||||
import { CloudRunStatus, RunCardFragment, RunCard_ChangeDocument } from '../generated/graphql'
|
||||
import { dayjs } from './utils/day.js'
|
||||
import { useDurationFormat } from '../composables/useDurationFormat'
|
||||
import { SolidStatusIcon, StatusType } from '@cypress-design/vue-statusicon'
|
||||
@@ -99,6 +99,15 @@ fragment RunCard on CloudRun {
|
||||
}
|
||||
`
|
||||
|
||||
gql`
|
||||
subscription RunCard_Change($id: ID!) {
|
||||
relevantRunSpecChange(runId: $id) {
|
||||
id
|
||||
...RunCard
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const STATUS_MAP: Record<CloudRunStatus, StatusType> = {
|
||||
PASSED: 'passed',
|
||||
FAILED: 'failed',
|
||||
@@ -116,6 +125,18 @@ const props = defineProps<{
|
||||
|
||||
const run = computed(() => props.gql)
|
||||
|
||||
const subscriptionVariables = computed(() => {
|
||||
return {
|
||||
id: run.value.id,
|
||||
}
|
||||
})
|
||||
|
||||
const shouldPauseSubscription = computed(() => {
|
||||
return run.value.status !== 'RUNNING'
|
||||
})
|
||||
|
||||
useSubscription({ query: RunCard_ChangeDocument, variables: subscriptionVariables, pause: shouldPauseSubscription })
|
||||
|
||||
const runUrl = computed(() => {
|
||||
return getUrlWithParams({
|
||||
url: run.value.url || '#',
|
||||
|
||||
8
packages/app/src/runs/RunsComposable.ts
Normal file
8
packages/app/src/runs/RunsComposable.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { RunCardFragment } from '../generated/graphql'
|
||||
|
||||
export type RunsComposable = {
|
||||
runs: Ref<RunCardFragment[] | undefined>
|
||||
reExecuteRunsQuery: () => void
|
||||
query: any
|
||||
}
|
||||
@@ -1,19 +1,10 @@
|
||||
import RunsContainer from './RunsContainer.vue'
|
||||
import { RunsContainerFragmentDoc } from '../generated/graphql-test'
|
||||
import { CloudUserStubs } from '@packages/graphql/test/stubCloudTypes'
|
||||
import { useUserProjectStatusStore } from '@packages/frontend-shared/src/store/user-project-status-store'
|
||||
|
||||
import { defaultMessages } from '@cy/i18n'
|
||||
|
||||
describe('<RunsContainer />', { keystrokeDelay: 0 }, () => {
|
||||
const cloudViewer = {
|
||||
...CloudUserStubs.me,
|
||||
organizations: null,
|
||||
firstOrganization: {
|
||||
nodes: [],
|
||||
},
|
||||
}
|
||||
|
||||
context('when the user is logged in', () => {
|
||||
beforeEach(() => {
|
||||
const userProjectStatusStore = useUserProjectStatusStore()
|
||||
@@ -23,11 +14,10 @@ describe('<RunsContainer />', { keystrokeDelay: 0 }, () => {
|
||||
|
||||
it('renders with expected runs if there is a cloud project id', () => {
|
||||
cy.mountFragment(RunsContainerFragmentDoc, {
|
||||
onResult: (result) => {
|
||||
result.cloudViewer = cloudViewer
|
||||
},
|
||||
render (gqlVal) {
|
||||
return <RunsContainer gql={gqlVal} online />
|
||||
const runs = gqlVal.currentProject?.cloudProject?.__typename === 'CloudProject' ? gqlVal.currentProject.cloudProject.runs?.nodes : undefined
|
||||
|
||||
return <RunsContainer gql={gqlVal} runs={runs} online />
|
||||
},
|
||||
})
|
||||
|
||||
@@ -43,7 +33,6 @@ describe('<RunsContainer />', { keystrokeDelay: 0 }, () => {
|
||||
it('renders instructions and "connect" link without a project id', () => {
|
||||
cy.mountFragment(RunsContainerFragmentDoc, {
|
||||
onResult: (result) => {
|
||||
result.cloudViewer = cloudViewer
|
||||
if (result.currentProject?.projectId) {
|
||||
result.currentProject.projectId = ''
|
||||
}
|
||||
@@ -79,19 +68,12 @@ describe('<RunsContainer />', { keystrokeDelay: 0 }, () => {
|
||||
|
||||
context('when the user has no recorded runs', () => {
|
||||
it('renders instructions and record prompt', () => {
|
||||
const userProjectStatusStore = useUserProjectStatusStore()
|
||||
|
||||
userProjectStatusStore.setUserFlag('isLoggedIn', true)
|
||||
cy.mountFragment(RunsContainerFragmentDoc, {
|
||||
onResult (gql) {
|
||||
gql.cloudViewer = cloudViewer
|
||||
if (gql.currentProject?.cloudProject?.__typename === 'CloudProject') {
|
||||
gql.currentProject.cloudProject.runs = {
|
||||
__typename: 'CloudRunConnection',
|
||||
pageInfo: null as any,
|
||||
nodes: [],
|
||||
}
|
||||
}
|
||||
},
|
||||
render (gqlVal) {
|
||||
return <RunsContainer gql={gqlVal} online />
|
||||
return <RunsContainer gql={gqlVal} runs={[]} online />
|
||||
},
|
||||
})
|
||||
|
||||
@@ -103,9 +85,11 @@ describe('<RunsContainer />', { keystrokeDelay: 0 }, () => {
|
||||
|
||||
context('with errors', () => {
|
||||
it('renders connection failed', () => {
|
||||
const userProjectStatusStore = useUserProjectStatusStore()
|
||||
|
||||
userProjectStatusStore.setUserFlag('isLoggedIn', true)
|
||||
cy.mountFragment(RunsContainerFragmentDoc, {
|
||||
onResult (result) {
|
||||
result.cloudViewer = cloudViewer
|
||||
result.currentProject!.cloudProject = null
|
||||
},
|
||||
render (gqlVal) {
|
||||
@@ -134,11 +118,11 @@ describe('<RunsContainer />', { keystrokeDelay: 0 }, () => {
|
||||
userProjectStatusStore.setUserFlag('isLoggedIn', true)
|
||||
|
||||
cy.mountFragment(RunsContainerFragmentDoc, {
|
||||
onResult: (result) => {
|
||||
result.cloudViewer = cloudViewer
|
||||
},
|
||||
render (gqlVal) {
|
||||
return <RunsContainer gql={gqlVal} online />
|
||||
const cloudProject = gqlVal.currentProject?.cloudProject?.__typename === 'CloudProject' ? gqlVal.currentProject.cloudProject : undefined
|
||||
const runs = cloudProject?.runs ? cloudProject.runs.nodes : undefined
|
||||
|
||||
return <RunsContainer gql={gqlVal} runs={runs} online />
|
||||
},
|
||||
})
|
||||
|
||||
@@ -153,11 +137,11 @@ describe('<RunsContainer />', { keystrokeDelay: 0 }, () => {
|
||||
userProjectStatusStore.setUserFlag('isLoggedIn', true)
|
||||
|
||||
cy.mountFragment(RunsContainerFragmentDoc, {
|
||||
onResult: (result) => {
|
||||
result.cloudViewer = cloudViewer
|
||||
},
|
||||
render (gqlVal) {
|
||||
return <RunsContainer gql={gqlVal} online />
|
||||
const cloudProject = gqlVal.currentProject?.cloudProject?.__typename === 'CloudProject' ? gqlVal.currentProject.cloudProject : undefined
|
||||
const runs = cloudProject?.runs ? cloudProject.runs.nodes : undefined
|
||||
|
||||
return <RunsContainer gql={gqlVal} runs={runs} online />
|
||||
},
|
||||
})
|
||||
|
||||
@@ -180,13 +164,13 @@ describe('<RunsContainer />', { keystrokeDelay: 0 }, () => {
|
||||
setProjectFlag('isConfigLoaded', true)
|
||||
setProjectFlag('isUsingGit', true)
|
||||
|
||||
expect(cloudStatusMatches('needsRecordedRun')).equals(true)
|
||||
expect(cloudStatusMatches('needsRecordedRun'), 'status should be needsRecordedRun').equals(true)
|
||||
cy.mountFragment(RunsContainerFragmentDoc, {
|
||||
onResult: (result) => {
|
||||
result.cloudViewer = cloudViewer
|
||||
},
|
||||
render (gqlVal) {
|
||||
return <RunsContainer gql={gqlVal} online />
|
||||
const cloudProject = gqlVal.currentProject?.cloudProject?.__typename === 'CloudProject' ? gqlVal.currentProject.cloudProject : undefined
|
||||
const runs = cloudProject?.runs ? cloudProject.runs.nodes : undefined
|
||||
|
||||
return <RunsContainer gql={gqlVal} runs={runs} online />
|
||||
},
|
||||
})
|
||||
|
||||
@@ -209,11 +193,11 @@ describe('<RunsContainer />', { keystrokeDelay: 0 }, () => {
|
||||
|
||||
expect(cloudStatusMatches('needsRecordedRun')).equals(true)
|
||||
cy.mountFragment(RunsContainerFragmentDoc, {
|
||||
onResult: (result) => {
|
||||
result.cloudViewer = cloudViewer
|
||||
},
|
||||
render (gqlVal) {
|
||||
return <RunsContainer gql={gqlVal} online />
|
||||
const cloudProject = gqlVal.currentProject?.cloudProject?.__typename === 'CloudProject' ? gqlVal.currentProject.cloudProject : undefined
|
||||
const runs = cloudProject?.runs ? cloudProject.runs.nodes : undefined
|
||||
|
||||
return <RunsContainer gql={gqlVal} runs={runs} online />
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
<RunsConnectSuccessAlert
|
||||
v-if="currentProject && showConnectSuccessAlert"
|
||||
:gql="currentProject"
|
||||
:class="{ 'absolute left-[24px] right-[24px] top-[24px]': currentProject?.cloudProject?.__typename === 'CloudProject' && !currentProject.cloudProject.runs?.nodes.length }"
|
||||
:class="{ 'absolute left-[24px] right-[24px] top-[24px]': currentProject?.cloudProject?.__typename === 'CloudProject' && !runs?.length }"
|
||||
/>
|
||||
|
||||
<RunsConnect
|
||||
v-if="!currentProject?.projectId || !cloudViewer?.id"
|
||||
:campaign="!cloudViewer?.id ? RUNS_PROMO_CAMPAIGNS.login : RUNS_PROMO_CAMPAIGNS.connectProject"
|
||||
v-if="!userProjectStatusStore.user.isLoggedIn || !currentProject?.projectId"
|
||||
:campaign="!userProjectStatusStore.user.isLoggedIn ? RUNS_PROMO_CAMPAIGNS.login : RUNS_PROMO_CAMPAIGNS.connectProject"
|
||||
/>
|
||||
<RunsErrorRenderer
|
||||
v-else-if="currentProject?.cloudProject?.__typename !== 'CloudProject' || connectionFailed"
|
||||
@@ -20,7 +20,7 @@
|
||||
/>
|
||||
|
||||
<RunsEmpty
|
||||
v-else-if="!currentProject?.cloudProject?.runs?.nodes.length"
|
||||
v-else-if="!runs?.length"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
@@ -61,7 +61,7 @@
|
||||
{{ t('runs.empty.ensureGitSetupCorrectly') }}
|
||||
</TrackedBanner>
|
||||
<RunCard
|
||||
v-for="run of currentProject?.cloudProject?.runs?.nodes"
|
||||
v-for="run of runs"
|
||||
:key="run.id"
|
||||
:gql="run"
|
||||
/>
|
||||
@@ -70,15 +70,13 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import { gql, useMutation } from '@urql/vue'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useI18n } from '@cy/i18n'
|
||||
import NoInternetConnection from '@packages/frontend-shared/src/components/NoInternetConnection.vue'
|
||||
import RunCard from './RunCard.vue'
|
||||
import RunsConnect from './RunsConnect.vue'
|
||||
import RunsConnectSuccessAlert from './RunsConnectSuccessAlert.vue'
|
||||
import RunsEmpty from './RunsEmpty.vue'
|
||||
import { RunsContainerFragment, RunsContainer_FetchNewerRunsDocument } from '../generated/graphql'
|
||||
import Warning from '@packages/frontend-shared/src/warning/Warning.vue'
|
||||
import RunsErrorRenderer from './RunsErrorRenderer.vue'
|
||||
import { useUserProjectStatusStore } from '@packages/frontend-shared/src/store/user-project-status-store'
|
||||
@@ -88,6 +86,7 @@ import { getUtmSource } from '@packages/frontend-shared/src/utils/getUtmSource'
|
||||
import TrackedBanner from '../specs/banners/TrackedBanner.vue'
|
||||
import { BannerIds } from '@packages/types/src'
|
||||
import { useMarkdown } from '@packages/frontend-shared/src/composables/useMarkdown'
|
||||
import type { RunCardFragment, RunsContainerFragment, RunsGitTreeQuery } from '../generated/graphql'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -99,128 +98,11 @@ const emit = defineEmits<{
|
||||
(e: 'reExecuteRunsQuery'): void
|
||||
}>()
|
||||
|
||||
gql`
|
||||
fragment RunsContainer_RunsConnection on CloudRunConnection {
|
||||
nodes {
|
||||
id
|
||||
...RunCard
|
||||
}
|
||||
pageInfo {
|
||||
startCursor
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
gql`
|
||||
fragment RunsContainer on Query {
|
||||
...RunsErrorRenderer
|
||||
currentProject {
|
||||
id
|
||||
projectId
|
||||
...RunsConnectSuccessAlert
|
||||
cloudProject {
|
||||
__typename
|
||||
... on CloudProject {
|
||||
id
|
||||
runs(first: 10) {
|
||||
...RunsContainer_RunsConnection
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cloudViewer {
|
||||
id
|
||||
}
|
||||
}`
|
||||
|
||||
gql`
|
||||
mutation RunsContainer_FetchNewerRuns(
|
||||
$cloudProjectNodeId: ID!,
|
||||
$beforeCursor: String,
|
||||
$hasBeforeCursor: Boolean!,
|
||||
$refreshPendingRuns: [ID!]!,
|
||||
$hasRefreshPendingRuns: Boolean!
|
||||
) {
|
||||
refetchRemote {
|
||||
cloudNode(id: $cloudProjectNodeId) {
|
||||
id
|
||||
__typename
|
||||
... on CloudProject {
|
||||
runs(first: 10) @skip(if: $hasBeforeCursor) {
|
||||
...RunsContainer_RunsConnection
|
||||
}
|
||||
newerRuns: runs(last: 10, before: $beforeCursor) @include(if: $hasBeforeCursor) {
|
||||
...RunsContainer_RunsConnection
|
||||
}
|
||||
}
|
||||
}
|
||||
cloudNodesByIds(ids: $refreshPendingRuns) @include(if: $hasRefreshPendingRuns) {
|
||||
id
|
||||
... on CloudRun {
|
||||
...RunCard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const currentProject = computed(() => props.gql.currentProject)
|
||||
const cloudViewer = computed(() => props.gql.cloudViewer)
|
||||
|
||||
const variables = computed(() => {
|
||||
if (currentProject.value?.cloudProject?.__typename === 'CloudProject') {
|
||||
const toRefresh = currentProject.value?.cloudProject.runs?.nodes?.map((r) => r.status === 'RUNNING' ? r.id : null).filter((f) => f) ?? []
|
||||
|
||||
return {
|
||||
cloudProjectNodeId: currentProject.value?.cloudProject.id,
|
||||
beforeCursor: currentProject.value?.cloudProject.runs?.pageInfo.startCursor,
|
||||
hasBeforeCursor: Boolean(currentProject.value?.cloudProject.runs?.pageInfo.startCursor),
|
||||
refreshPendingRuns: toRefresh,
|
||||
hasRefreshPendingRuns: toRefresh.length > 0,
|
||||
}
|
||||
}
|
||||
|
||||
return undefined as any
|
||||
})
|
||||
|
||||
const refetcher = useMutation(RunsContainer_FetchNewerRunsDocument)
|
||||
|
||||
// 15 seconds polling
|
||||
const POLL_FOR_LATEST = 1000 * 15
|
||||
let timeout: null | number = null
|
||||
|
||||
function startPolling () {
|
||||
timeout = window.setTimeout(function fetchNewerRuns () {
|
||||
if (variables.value && props.online) {
|
||||
refetcher.executeMutation(variables.value)
|
||||
.then(() => {
|
||||
startPolling()
|
||||
})
|
||||
} else {
|
||||
startPolling()
|
||||
}
|
||||
}, POLL_FOR_LATEST)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Always fetch when the component mounts, and we're not already fetching
|
||||
if (props.online && !refetcher.fetching) {
|
||||
refetcher.executeMutation(variables.value)
|
||||
}
|
||||
|
||||
startPolling()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
|
||||
timeout = null
|
||||
})
|
||||
|
||||
const props = defineProps<{
|
||||
gql: RunsContainerFragment
|
||||
gql: RunsContainerFragment | RunsGitTreeQuery
|
||||
runs?: RunCardFragment[]
|
||||
online: boolean
|
||||
}>()
|
||||
|
||||
|
||||
64
packages/app/src/runs/useGitTreeRuns.ts
Normal file
64
packages/app/src/runs/useGitTreeRuns.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { gql, useQuery } from '@urql/vue'
|
||||
import { Ref, computed } from 'vue'
|
||||
import { RunsGitTreeDocument, RunCardFragment } from '../generated/graphql'
|
||||
import { useRelevantRun } from '../composables/useRelevantRun'
|
||||
import type { RunsComposable } from './RunsComposable'
|
||||
|
||||
gql`
|
||||
query RunsGitTree($runIds: [ID!]!) {
|
||||
...RunsGitTreeProject
|
||||
}
|
||||
`
|
||||
|
||||
gql `
|
||||
fragment RunsGitTreeProject on Query {
|
||||
...RunsErrorRenderer
|
||||
currentProject {
|
||||
id
|
||||
projectId
|
||||
...RunsConnectSuccessAlert
|
||||
cloudProject {
|
||||
__typename
|
||||
... on CloudProject {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
cloudNodesByIds(ids: $runIds) {
|
||||
id
|
||||
...RunCard
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const useGitTreeRuns = (online: Ref<boolean>): RunsComposable => {
|
||||
const relevantRuns = useRelevantRun('RUNS')
|
||||
|
||||
const variables = computed(() => {
|
||||
return {
|
||||
runIds: relevantRuns?.value.latest?.map((run) => run.runId) || [],
|
||||
}
|
||||
})
|
||||
|
||||
const shouldPauseQuery = computed(() => {
|
||||
return !variables.value.runIds
|
||||
})
|
||||
|
||||
const query = useQuery({ query: RunsGitTreeDocument, variables, pause: shouldPauseQuery, requestPolicy: 'network-only' })
|
||||
|
||||
const runs = computed(() => {
|
||||
const nodes = query.data.value?.cloudNodesByIds?.filter((val): val is RunCardFragment => val?.__typename === 'CloudRun')
|
||||
|
||||
return nodes
|
||||
})
|
||||
|
||||
function reExecuteRunsQuery () {
|
||||
query.executeQuery()
|
||||
}
|
||||
|
||||
return {
|
||||
runs,
|
||||
reExecuteRunsQuery,
|
||||
query,
|
||||
}
|
||||
}
|
||||
128
packages/app/src/runs/useProjectRuns.ts
Normal file
128
packages/app/src/runs/useProjectRuns.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { gql, useMutation, useQuery } from '@urql/vue'
|
||||
import { Ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { RunsDocument, RunsContainer_FetchNewerRunsDocument, RunCardFragment } from '../generated/graphql'
|
||||
import type { RunsComposable } from './RunsComposable'
|
||||
|
||||
gql`
|
||||
query Runs {
|
||||
...RunsContainer
|
||||
}`
|
||||
|
||||
gql`
|
||||
fragment RunsContainer_RunsConnection on CloudRunConnection {
|
||||
nodes {
|
||||
id
|
||||
...RunCard
|
||||
}
|
||||
pageInfo {
|
||||
startCursor
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
gql`
|
||||
fragment RunsContainer on Query {
|
||||
...RunsErrorRenderer
|
||||
currentProject {
|
||||
id
|
||||
projectId
|
||||
...RunsConnectSuccessAlert
|
||||
cloudProject {
|
||||
__typename
|
||||
... on CloudProject {
|
||||
id
|
||||
runs(first: 10) {
|
||||
...RunsContainer_RunsConnection
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
gql`
|
||||
mutation RunsContainer_FetchNewerRuns(
|
||||
$cloudProjectNodeId: ID!,
|
||||
$beforeCursor: String,
|
||||
$hasBeforeCursor: Boolean!,
|
||||
) {
|
||||
refetchRemote {
|
||||
cloudNode(id: $cloudProjectNodeId) {
|
||||
id
|
||||
__typename
|
||||
... on CloudProject {
|
||||
runs(first: 10) @skip(if: $hasBeforeCursor) {
|
||||
...RunsContainer_RunsConnection
|
||||
}
|
||||
newerRuns: runs(last: 10, before: $beforeCursor) @include(if: $hasBeforeCursor) {
|
||||
...RunsContainer_RunsConnection
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const useProjectRuns = (online: Ref<boolean>): RunsComposable => {
|
||||
const query = useQuery({ query: RunsDocument, requestPolicy: 'network-only' })
|
||||
|
||||
const currentProject = computed(() => query.data.value?.currentProject)
|
||||
const runs = computed(() => query.data.value?.currentProject?.cloudProject?.__typename === 'CloudProject' ? query.data.value?.currentProject?.cloudProject?.runs?.nodes as RunCardFragment[] : [])
|
||||
|
||||
const variables = computed(() => {
|
||||
if (currentProject.value?.cloudProject?.__typename === 'CloudProject') {
|
||||
return {
|
||||
cloudProjectNodeId: currentProject.value?.cloudProject.id,
|
||||
beforeCursor: currentProject.value?.cloudProject.runs?.pageInfo.startCursor,
|
||||
hasBeforeCursor: Boolean(currentProject.value?.cloudProject.runs?.pageInfo.startCursor),
|
||||
}
|
||||
}
|
||||
|
||||
return undefined as any
|
||||
})
|
||||
|
||||
const refetcher = useMutation(RunsContainer_FetchNewerRunsDocument)
|
||||
|
||||
// 15 seconds polling
|
||||
const POLL_FOR_LATEST = 1000 * 15
|
||||
let timeout: null | number = null
|
||||
|
||||
function startPolling () {
|
||||
timeout = window.setTimeout(function fetchNewerRuns () {
|
||||
if (variables.value && online.value) {
|
||||
refetcher.executeMutation(variables.value)
|
||||
.then(() => {
|
||||
startPolling()
|
||||
})
|
||||
} else {
|
||||
startPolling()
|
||||
}
|
||||
}, POLL_FOR_LATEST)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Always fetch when the component mounts, and we're not already fetching
|
||||
if (online.value && !refetcher.fetching) {
|
||||
refetcher.executeMutation(variables.value)
|
||||
}
|
||||
|
||||
startPolling()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (timeout) {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
|
||||
timeout = null
|
||||
})
|
||||
|
||||
function reExecuteRunsQuery () {
|
||||
query.executeQuery()
|
||||
}
|
||||
|
||||
return {
|
||||
runs,
|
||||
reExecuteRunsQuery,
|
||||
query,
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ describe('<AverageDuration />', () => {
|
||||
data: {
|
||||
__typename: 'CloudProjectSpec',
|
||||
id: 'id',
|
||||
averageDuration: duration,
|
||||
averageDurationForRunIds: duration,
|
||||
retrievedAt: new Date().toISOString(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="props.gql?.data?.__typename === 'CloudProjectSpec' && props.gql?.data?.averageDuration"
|
||||
v-if="props.gql?.data?.__typename === 'CloudProjectSpec' && props.gql?.data?.averageDurationForRunIds"
|
||||
class="h-full grid text-gray-700 justify-end items-center"
|
||||
data-cy="average-duration"
|
||||
>
|
||||
{{ getDurationString(props.gql.data.averageDuration) }}
|
||||
{{ getDurationString(props.gql.data.averageDurationForRunIds) }}
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
@@ -30,7 +30,7 @@ fragment AverageDuration on RemoteFetchableCloudProjectSpecResult {
|
||||
... on CloudProjectSpec {
|
||||
id
|
||||
retrievedAt
|
||||
averageDuration(fromBranch: $fromBranch)
|
||||
averageDurationForRunIds(cloudRunIds: $runIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,9 @@ function mountWithRuns (runs: Required<CloudSpecRun>[]) {
|
||||
__typename: 'CloudProjectSpec',
|
||||
retrievedAt: new Date().toISOString(),
|
||||
id: 'id',
|
||||
specRuns: {
|
||||
nodes: [
|
||||
...runs as any, // suppress TS compiler
|
||||
],
|
||||
},
|
||||
specRunsForRunIds: [
|
||||
...runs as any, // suppress TS compiler
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -92,9 +92,6 @@ import SpecRunSummary from './SpecRunSummary.vue'
|
||||
import { gql } from '@urql/vue'
|
||||
import { getUrlWithParams } from '@packages/frontend-shared/src/utils/getUrlWithParams'
|
||||
|
||||
// cloudProjectSpec.specRuns was marked deprecated in the cloud in favor of a new
|
||||
// field. When the work is completed to use that field, remove this eslist-disable comment
|
||||
/* eslint-disable graphql/no-deprecated-fields */
|
||||
gql`
|
||||
fragment RunStatusDots on RemoteFetchableCloudProjectSpecResult {
|
||||
id
|
||||
@@ -108,38 +105,36 @@ fragment RunStatusDots on RemoteFetchableCloudProjectSpecResult {
|
||||
... on CloudProjectSpec {
|
||||
id
|
||||
retrievedAt
|
||||
specRuns(first: 4, fromBranch: $fromBranch) {
|
||||
nodes {
|
||||
id
|
||||
runNumber
|
||||
basename
|
||||
path
|
||||
extension
|
||||
testsFailed{
|
||||
min
|
||||
max
|
||||
}
|
||||
testsPassed{
|
||||
min
|
||||
max
|
||||
}
|
||||
testsPending{
|
||||
min
|
||||
max
|
||||
}
|
||||
testsSkipped{
|
||||
min
|
||||
max
|
||||
}
|
||||
createdAt
|
||||
groupCount
|
||||
specDuration{
|
||||
min
|
||||
max
|
||||
}
|
||||
status
|
||||
url
|
||||
specRunsForRunIds(cloudRunIds: $runIds) {
|
||||
id
|
||||
runNumber
|
||||
basename
|
||||
path
|
||||
extension
|
||||
testsFailed{
|
||||
min
|
||||
max
|
||||
}
|
||||
testsPassed{
|
||||
min
|
||||
max
|
||||
}
|
||||
testsPending{
|
||||
min
|
||||
max
|
||||
}
|
||||
testsSkipped{
|
||||
min
|
||||
max
|
||||
}
|
||||
createdAt
|
||||
groupCount
|
||||
specDuration{
|
||||
min
|
||||
max
|
||||
}
|
||||
status
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +148,7 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
const runs = computed(() => {
|
||||
return props.gql?.data?.__typename === 'CloudProjectSpec' ? props.gql?.data?.specRuns?.nodes ?? [] : []
|
||||
return props.gql?.data?.__typename === 'CloudProjectSpec' ? props.gql?.data?.specRunsForRunIds ?? [] : []
|
||||
})
|
||||
|
||||
const isRunsLoaded = computed(() => {
|
||||
|
||||
@@ -11,10 +11,10 @@ describe('<SpecsList />', { keystrokeDelay: 0 }, () => {
|
||||
|
||||
return cy.mountFragment(Specs_SpecsListFragmentDoc, {
|
||||
variableTypes: {
|
||||
hasBranch: 'Boolean',
|
||||
hasRunIds: 'Boolean',
|
||||
},
|
||||
variables: {
|
||||
hasBranch: true,
|
||||
hasRunIds: false,
|
||||
},
|
||||
onResult: (ctx) => {
|
||||
if (!ctx.currentProject) throw new Error('need current project')
|
||||
|
||||
@@ -297,7 +297,7 @@ fragment SpecsList on Spec {
|
||||
gitInfo {
|
||||
...SpecListRow
|
||||
}
|
||||
cloudSpec(name: "cloudSpec") @include(if: $hasBranch) {
|
||||
cloudSpec(name: "cloudSpec") @include(if: $hasRunIds) {
|
||||
id
|
||||
fetchingStatus
|
||||
...AverageDuration
|
||||
@@ -432,7 +432,6 @@ const mostRecentUpdateRef = toRef(props, 'mostRecentUpdate')
|
||||
const { refetchFailedCloudData } = useCloudSpecData(
|
||||
isProjectDisconnected,
|
||||
isOffline,
|
||||
props.gql.currentProject?.projectId,
|
||||
mostRecentUpdateRef,
|
||||
displayedSpecs,
|
||||
props.gql.currentProject?.specs as SpecsListFragment[] || [],
|
||||
|
||||
50
packages/app/src/specs/SpecsListRunWatcher.vue
Normal file
50
packages/app/src/specs/SpecsListRunWatcher.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
Non rendering component used to watch running runs and emit an event
|
||||
when a run has changed. Used by the specs list to update spec information
|
||||
during a RUNNING run.
|
||||
*/
|
||||
<script setup lang="ts">
|
||||
import { gql, useSubscription } from '@urql/vue'
|
||||
import { computed } from 'vue'
|
||||
import type { CloudRunStatus } from '../generated/graphql'
|
||||
import { SpecsListRunWatcherDocument } from '../generated/graphql'
|
||||
|
||||
/**
|
||||
* Subscription to watch a run.
|
||||
* Values being queried are there to tell the polling source
|
||||
* which fields to watch for changes
|
||||
*/
|
||||
gql`
|
||||
subscription SpecsListRunWatcher($id: ID!) {
|
||||
relevantRunSpecChange(runId: $id) {
|
||||
id
|
||||
status
|
||||
totalInstanceCount
|
||||
completedInstanceCount
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const props = defineProps<{
|
||||
run: {runId: string, status: CloudRunStatus | null }
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(eventName: 'runUpdate'): void
|
||||
}>()
|
||||
|
||||
const variables = computed(() => {
|
||||
return { id: props.run.runId }
|
||||
})
|
||||
|
||||
const shouldPause = computed(() => {
|
||||
return props.run.status !== 'RUNNING'
|
||||
})
|
||||
|
||||
const handleUpdate = () => {
|
||||
emits('runUpdate')
|
||||
}
|
||||
|
||||
useSubscription({ query: SpecsListRunWatcherDocument, variables, pause: shouldPause }, handleUpdate)
|
||||
|
||||
</script>
|
||||
@@ -22,10 +22,14 @@ describe('<FlakyInformation />', () => {
|
||||
data: {
|
||||
__typename: 'CloudProjectSpec',
|
||||
id: '3',
|
||||
isConsideredFlaky: flaky,
|
||||
flakyStatus: {
|
||||
isConsideredFlakyForRunIds: flaky,
|
||||
flakyStatusForRunIds: {
|
||||
__typename: 'CloudProjectSpecFlakyStatus',
|
||||
dashboardUrl: '#',
|
||||
flakyRuns: 1,
|
||||
flakyRunsWindow: 5,
|
||||
lastFlaky: 3,
|
||||
severity: 'LOW',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -20,12 +20,13 @@
|
||||
:href="cloudUrl"
|
||||
class="hocus:no-underline"
|
||||
>
|
||||
<FlakySpecSummaryAdapter
|
||||
:project-id="props.projectGql.projectId"
|
||||
:from-branch="props.projectGql?.branch || ''"
|
||||
:spec-path="props.specGql.relative"
|
||||
<FlakySpecSummary
|
||||
:spec-name="props.specGql?.fileName ?? ''"
|
||||
:spec-extension="props.specGql?.specFileExtension ?? ''"
|
||||
:severity="flakyStatus?.severity ?? 'NONE'"
|
||||
:total-runs="flakyStatus?.flakyRunsWindow ?? 0"
|
||||
:total-flaky-runs="flakyStatus?.flakyRuns ?? 0"
|
||||
:runs-since-last-flake="flakyStatus?.lastFlaky ?? 0"
|
||||
/>
|
||||
</ExternalLink>
|
||||
</template>
|
||||
@@ -39,8 +40,8 @@ import type { FlakyInformationProjectFragment, FlakyInformationSpecFragment, Fla
|
||||
import { gql } from '@urql/vue'
|
||||
import { computed } from 'vue'
|
||||
import Tooltip from '@packages/frontend-shared/src/components/Tooltip.vue'
|
||||
import FlakySpecSummaryAdapter from './FlakySpecSummaryAdapter.vue'
|
||||
import FlakyBadge from './FlakyBadge.vue'
|
||||
import FlakySpecSummary from './FlakySpecSummary.vue'
|
||||
import { getUrlWithParams } from '@packages/frontend-shared/src/utils/getUrlWithParams'
|
||||
|
||||
gql`
|
||||
@@ -66,11 +67,15 @@ fragment FlakyInformationCloudSpec on RemoteFetchableCloudProjectSpecResult {
|
||||
data {
|
||||
... on CloudProjectSpec {
|
||||
id
|
||||
isConsideredFlaky(fromBranch: $fromBranch)
|
||||
flakyStatus(fromBranch: $fromBranch, flakyRunsWindow: 50) {
|
||||
isConsideredFlakyForRunIds(cloudRunIds: $runIds)
|
||||
flakyStatusForRunIds(cloudRunIds: $runIds) {
|
||||
__typename
|
||||
... on CloudProjectSpecFlakyStatus {
|
||||
dashboardUrl
|
||||
severity
|
||||
flakyRuns
|
||||
flakyRunsWindow
|
||||
lastFlaky
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,13 +89,12 @@ const props = defineProps<{
|
||||
cloudSpecGql: FlakyInformationCloudSpecFragment | null | undefined
|
||||
}>()
|
||||
|
||||
const isFlaky = computed(() => props.cloudSpecGql?.data?.__typename === 'CloudProjectSpec' && !!props.cloudSpecGql?.data?.isConsideredFlaky)
|
||||
const cloudSpec = computed(() => props.cloudSpecGql?.data?.__typename === 'CloudProjectSpec' ? props.cloudSpecGql.data : null)
|
||||
const isFlaky = computed(() => !!cloudSpec.value?.isConsideredFlakyForRunIds)
|
||||
const flakyStatus = computed(() => cloudSpec.value?.flakyStatusForRunIds?.__typename === 'CloudProjectSpecFlakyStatus' ? cloudSpec.value?.flakyStatusForRunIds : null)
|
||||
const cloudUrl = computed(() => {
|
||||
const cloudSpec = props.cloudSpecGql?.data?.__typename === 'CloudProjectSpec' ? props.cloudSpecGql.data : null
|
||||
const flakyStatus = cloudSpec?.flakyStatus?.__typename === 'CloudProjectSpecFlakyStatus' ? cloudSpec.flakyStatus : null
|
||||
|
||||
return getUrlWithParams({
|
||||
url: flakyStatus?.dashboardUrl || '#',
|
||||
url: flakyStatus.value?.dashboardUrl || '#',
|
||||
params: {
|
||||
utm_medium: 'Specs Flake Annotation Badge',
|
||||
utm_campaign: 'Flaky',
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
<template>
|
||||
<FlakySpecSummary
|
||||
:spec-name="specName"
|
||||
:spec-extension="specExtension"
|
||||
:severity="flakyStatus?.severity ?? 'NONE'"
|
||||
:total-runs="flakyStatus?.flakyRunsWindow ?? 0"
|
||||
:total-flaky-runs="flakyStatus?.flakyRuns ?? 0"
|
||||
:runs-since-last-flake="flakyStatus?.lastFlaky ?? 0"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { computed, onBeforeMount } from 'vue'
|
||||
import FlakySpecSummary from './FlakySpecSummary.vue'
|
||||
import { gql, useMutation, useQuery } from '@urql/vue'
|
||||
import { FlakySpecSummaryQueryDocument, PurgeCloudSpecCacheDocument } from '../../generated/graphql'
|
||||
|
||||
gql`
|
||||
fragment FlakySpecSummaryQueryData on Query {
|
||||
cloudSpecByPath(projectSlug: $projectId, specPath: $specPath) {
|
||||
__typename
|
||||
... on CloudProjectSpec {
|
||||
id
|
||||
flakyStatus(fromBranch: $fromBranch, flakyRunsWindow: 50) {
|
||||
__typename
|
||||
... on CloudProjectSpecFlakyStatus {
|
||||
severity
|
||||
flakyRuns
|
||||
flakyRunsWindow
|
||||
lastFlaky
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
gql`
|
||||
query FlakySpecSummaryQuery($projectId: String!, $specPath: String!, $fromBranch: String!) {
|
||||
...FlakySpecSummaryQueryData
|
||||
}
|
||||
`
|
||||
|
||||
gql`
|
||||
mutation PurgeCloudSpecCache ($projectSlug: String!, $specPaths: [String!]!) {
|
||||
purgeCloudSpecByPathCache(projectSlug: $projectSlug, specPaths: $specPaths)
|
||||
}
|
||||
`
|
||||
|
||||
const props = defineProps<{
|
||||
projectId: string
|
||||
specName: string
|
||||
specExtension: string
|
||||
specPath: string
|
||||
fromBranch: string
|
||||
}>()
|
||||
|
||||
const variables = computed(() => {
|
||||
return {
|
||||
projectId: props.projectId,
|
||||
specPath: props.specPath,
|
||||
fromBranch: props.fromBranch,
|
||||
}
|
||||
})
|
||||
|
||||
const query = useQuery({ query: FlakySpecSummaryQueryDocument, variables, pause: true })
|
||||
const purgeCloudSpecCacheMutation = useMutation(PurgeCloudSpecCacheDocument)
|
||||
|
||||
const flakyStatus = computed(() => {
|
||||
if (query.data.value?.cloudSpecByPath?.__typename === 'CloudProjectSpec' &&
|
||||
query.data.value?.cloudSpecByPath?.flakyStatus?.__typename === 'CloudProjectSpecFlakyStatus') {
|
||||
return query.data.value.cloudSpecByPath.flakyStatus
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
onBeforeMount(async () => {
|
||||
// Ensure we have the latest flaky data - since we manually query for flaky data here the background polling
|
||||
// won't auto-refetch it for us when stale data is detected based on lastProjectUpdate
|
||||
await purgeCloudSpecCacheMutation.executeMutation({ projectSlug: props.projectId, specPaths: [props.specPath] })
|
||||
await query.executeQuery({ requestPolicy: 'network-only' })
|
||||
})
|
||||
|
||||
</script>
|
||||
@@ -32,6 +32,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys 1
|
||||
},
|
||||
'env': {},
|
||||
'execTimeout': 60000,
|
||||
'experimentalCspAllowList': false,
|
||||
'experimentalFetchPolyfill': false,
|
||||
'experimentalInteractiveRunEvents': false,
|
||||
'experimentalRunAllSpecs': false,
|
||||
@@ -119,6 +120,7 @@ exports['config/src/index .getDefaultValues returns list of public config keys f
|
||||
},
|
||||
'env': {},
|
||||
'execTimeout': 60000,
|
||||
'experimentalCspAllowList': false,
|
||||
'experimentalFetchPolyfill': false,
|
||||
'experimentalInteractiveRunEvents': false,
|
||||
'experimentalRunAllSpecs': false,
|
||||
@@ -202,6 +204,7 @@ exports['config/src/index .getPublicConfigKeys returns list of public config key
|
||||
'e2e',
|
||||
'env',
|
||||
'execTimeout',
|
||||
'experimentalCspAllowList',
|
||||
'experimentalFetchPolyfill',
|
||||
'experimentalInteractiveRunEvents',
|
||||
'experimentalRunAllSpecs',
|
||||
|
||||
@@ -225,6 +225,31 @@ exports['config/src/validation .isStringOrFalse returns error message when value
|
||||
'type': 'a string or false',
|
||||
}
|
||||
|
||||
exports['not an array error message'] = {
|
||||
'key': 'fakeKey',
|
||||
'value': 'fakeValue',
|
||||
'type': 'an array including any of these values: [true, false]',
|
||||
}
|
||||
|
||||
exports['not a subset of error message'] = {
|
||||
'key': 'fakeKey',
|
||||
'value': [
|
||||
null,
|
||||
],
|
||||
'type': 'an array including any of these values: ["fakeValue", "fakeValue1", "fakeValue2"]',
|
||||
}
|
||||
|
||||
exports['not all in subset error message'] = {
|
||||
'key': 'fakeKey',
|
||||
'value': [
|
||||
'fakeValue',
|
||||
'fakeValue1',
|
||||
'fakeValue2',
|
||||
'fakeValue3',
|
||||
],
|
||||
'type': 'an array including any of these values: ["fakeValue", "fakeValue1", "fakeValue2"]',
|
||||
}
|
||||
|
||||
exports['invalid lower bound'] = {
|
||||
'key': 'test',
|
||||
'value': -1,
|
||||
|
||||
@@ -196,6 +196,12 @@ const driverConfigOptions: Array<DriverConfigOption> = [
|
||||
defaultValue: 60000,
|
||||
validation: validate.isNumber,
|
||||
overrideLevel: 'any',
|
||||
}, {
|
||||
name: 'experimentalCspAllowList',
|
||||
defaultValue: false,
|
||||
validation: validate.validateAny(validate.isBoolean, validate.isArrayIncludingAny('script-src-elem', 'script-src', 'default-src', 'form-action', 'child-src', 'frame-src')),
|
||||
overrideLevel: 'never',
|
||||
requireRestartOnChange: 'server',
|
||||
}, {
|
||||
name: 'experimentalFetchPolyfill',
|
||||
defaultValue: false,
|
||||
|
||||
@@ -33,7 +33,7 @@ const _isFullyQualifiedUrl = (value: any): ErrResult | boolean => {
|
||||
return _.isString(value) && /^https?\:\/\//.test(value)
|
||||
}
|
||||
|
||||
const isArrayOfStrings = (value: any): ErrResult | boolean => {
|
||||
const isStringArray = (value: any): ErrResult | boolean => {
|
||||
return _.isArray(value) && _.every(value, _.isString)
|
||||
}
|
||||
|
||||
@@ -41,6 +41,21 @@ const isFalse = (value: any): boolean => {
|
||||
return value === false
|
||||
}
|
||||
|
||||
type ValidationResult = ErrResult | boolean | string;
|
||||
type ValidationFn = (key: string, value: any) => ValidationResult
|
||||
|
||||
export const validateAny = (...validations: ValidationFn[]): ValidationFn => {
|
||||
return (key: string, value: any): ValidationResult => {
|
||||
return validations.reduce((result: ValidationResult, validation: ValidationFn) => {
|
||||
if (result === true) {
|
||||
return result
|
||||
}
|
||||
|
||||
return validation(key, value)
|
||||
}, false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a single browser object.
|
||||
* @returns {string|true} Returns `true` if the object is matching browser object schema. Returns an error message if it does not.
|
||||
@@ -148,6 +163,29 @@ export const isOneOf = (...values: any[]): ((key: string, value: any) => ErrResu
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given array value for a key includes only members of the provided values.
|
||||
* @example
|
||||
```
|
||||
validate = v.isArrayIncludingAny("foo", "bar", "baz")
|
||||
validate("example", ["foo"]) // true
|
||||
validate("example", ["bar", "baz"]) // true
|
||||
validate("example", ["foo", "else"]) // error message string
|
||||
validate("example", ["foo", "bar", "baz", "else"]) // error message string
|
||||
```
|
||||
*/
|
||||
export const isArrayIncludingAny = (...values: any[]): ((key: string, value: any) => ErrResult | true) => {
|
||||
const validValues = values.map((a) => str(a)).join(', ')
|
||||
|
||||
return (key, value) => {
|
||||
if (!Array.isArray(value) || !value.every((v) => values.includes(v))) {
|
||||
return errMsg(key, value, `an array including any of these values: [${validValues}]`)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the supplied set of cert information is valid
|
||||
* @returns {string|true} Returns `true` if the information set is valid. Returns an error message if it is not.
|
||||
@@ -332,7 +370,7 @@ export function isFullyQualifiedUrl (key: string, value: any): ErrResult | true
|
||||
}
|
||||
|
||||
export function isStringOrArrayOfStrings (key: string, value: any): ErrResult | true {
|
||||
if (_.isString(value) || isArrayOfStrings(value)) {
|
||||
if (_.isString(value) || isStringArray(value)) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -340,7 +378,7 @@ export function isStringOrArrayOfStrings (key: string, value: any): ErrResult |
|
||||
}
|
||||
|
||||
export function isNullOrArrayOfStrings (key: string, value: any): ErrResult | true {
|
||||
if (_.isNull(value) || isArrayOfStrings(value)) {
|
||||
if (_.isNull(value) || isStringArray(value)) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -838,6 +838,34 @@ describe('config/src/project/utils', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('experimentalCspAllowList=false', function () {
|
||||
return this.defaults('experimentalCspAllowList', false)
|
||||
})
|
||||
|
||||
it('experimentalCspAllowList=true', function () {
|
||||
return this.defaults('experimentalCspAllowList', true, {
|
||||
experimentalCspAllowList: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('experimentalCspAllowList=[]', function () {
|
||||
return this.defaults('experimentalCspAllowList', [], {
|
||||
experimentalCspAllowList: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('experimentalCspAllowList=default-src|script-src', function () {
|
||||
return this.defaults('experimentalCspAllowList', ['default-src', 'script-src'], {
|
||||
experimentalCspAllowList: ['default-src', 'script-src'],
|
||||
})
|
||||
})
|
||||
|
||||
it('experimentalCspAllowList=["default-src","script-src"]', function () {
|
||||
return this.defaults('experimentalCspAllowList', ['default-src', 'script-src'], {
|
||||
experimentalCspAllowList: ['default-src', 'script-src'],
|
||||
})
|
||||
})
|
||||
|
||||
it('resets numTestsKeptInMemory to 0 when runMode', function () {
|
||||
return mergeDefaults({ projectRoot: '/foo/bar/', supportFile: false }, { isTextTerminal: true }, {}, this.getFilesByGlob)
|
||||
.then((cfg) => {
|
||||
@@ -1032,6 +1060,7 @@ describe('config/src/project/utils', () => {
|
||||
execTimeout: { value: 60000, from: 'default' },
|
||||
experimentalModifyObstructiveThirdPartyCode: { value: false, from: 'default' },
|
||||
experimentalSkipDomainInjection: { value: null, from: 'default' },
|
||||
experimentalCspAllowList: { value: false, from: 'default' },
|
||||
experimentalFetchPolyfill: { value: false, from: 'default' },
|
||||
experimentalInteractiveRunEvents: { value: false, from: 'default' },
|
||||
experimentalMemoryManagement: { value: false, from: 'default' },
|
||||
@@ -1127,6 +1156,7 @@ describe('config/src/project/utils', () => {
|
||||
execTimeout: { value: 60000, from: 'default' },
|
||||
experimentalModifyObstructiveThirdPartyCode: { value: false, from: 'default' },
|
||||
experimentalSkipDomainInjection: { value: null, from: 'default' },
|
||||
experimentalCspAllowList: { value: false, from: 'default' },
|
||||
experimentalFetchPolyfill: { value: false, from: 'default' },
|
||||
experimentalInteractiveRunEvents: { value: false, from: 'default' },
|
||||
experimentalMemoryManagement: { value: false, from: 'default' },
|
||||
|
||||
@@ -6,6 +6,39 @@ import * as validation from '../src/validation'
|
||||
describe('config/src/validation', () => {
|
||||
const mockKey = 'mockConfigKey'
|
||||
|
||||
describe('.validateAny', () => {
|
||||
it('returns new validation function that accepts 2 arguments', () => {
|
||||
const validate = validation.validateAny(() => true, () => false)
|
||||
|
||||
expect(validate).to.be.a.instanceof(Function)
|
||||
expect(validate.length).to.eq(2)
|
||||
})
|
||||
|
||||
it('returned validation function will return true when any validations pass', () => {
|
||||
const value = Date.now()
|
||||
const key = `key_${value}`
|
||||
const validatePass1 = validation.validateAny((k, v) => `${value}`, (k, v) => true)
|
||||
|
||||
expect(validatePass1(key, value)).to.equal(true)
|
||||
|
||||
const validatePass2 = validation.validateAny((k, v) => true, (k, v) => `${value}`)
|
||||
|
||||
expect(validatePass2(key, value)).to.equal(true)
|
||||
})
|
||||
|
||||
it('returned validation function will return last failure result when all validations fail', () => {
|
||||
const value = Date.now()
|
||||
const key = `key_${value}`
|
||||
const validateFail1 = validation.validateAny((k, v) => `${value}`, (k, v) => false)
|
||||
|
||||
expect(validateFail1(key, value)).to.equal(false)
|
||||
|
||||
const validateFail2 = validation.validateAny((k, v) => false, (k, v) => `${value}`)
|
||||
|
||||
expect(validateFail2(key, value)).to.equal(`${value}`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isValidClientCertificatesSet', () => {
|
||||
it('returns error message for certs not passed as an array array', () => {
|
||||
const result = validation.isValidRetriesConfig(mockKey, '1')
|
||||
@@ -389,6 +422,54 @@ describe('config/src/validation', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isArrayIncludingAny', () => {
|
||||
it('returns new validation function that accepts 2 arguments', () => {
|
||||
const validate = validation.isArrayIncludingAny(true, false)
|
||||
|
||||
expect(validate).to.be.a.instanceof(Function)
|
||||
expect(validate.length).to.eq(2)
|
||||
})
|
||||
|
||||
it('returned validation function will return true when value is a subset of the provided values', () => {
|
||||
const value = 'fakeValue'
|
||||
const key = 'fakeKey'
|
||||
const validatePass1 = validation.isArrayIncludingAny(true, false)
|
||||
|
||||
expect(validatePass1(key, [false])).to.equal(true)
|
||||
|
||||
const validatePass2 = validation.isArrayIncludingAny(value, value + 1, value + 2)
|
||||
|
||||
expect(validatePass2(key, [value])).to.equal(true)
|
||||
})
|
||||
|
||||
it('returned validation function will fail if values is not an array', () => {
|
||||
const value = 'fakeValue'
|
||||
const key = 'fakeKey'
|
||||
const validateFail = validation.isArrayIncludingAny(true, false)
|
||||
|
||||
let msg = validateFail(key, value)
|
||||
|
||||
expect(msg).to.not.be.true
|
||||
snapshot('not an array error message', msg)
|
||||
})
|
||||
|
||||
it('returned validation function will fail if any values are not present in the provided values', () => {
|
||||
const value = 'fakeValue'
|
||||
const key = 'fakeKey'
|
||||
const validateFail = validation.isArrayIncludingAny(value, value + 1, value + 2)
|
||||
|
||||
let msg = validateFail(key, [null])
|
||||
|
||||
expect(msg).to.not.be.true
|
||||
snapshot('not a subset of error message', msg)
|
||||
|
||||
msg = validateFail(key, [value, value + 1, value + 2, value + 3])
|
||||
|
||||
expect(msg).to.not.be.true
|
||||
snapshot('not all in subset error message', msg)
|
||||
})
|
||||
})
|
||||
|
||||
describe('.isValidCrfOrBoolean', () => {
|
||||
it('validates booleans', () => {
|
||||
const validate = validation.isValidCrfOrBoolean
|
||||
|
||||
@@ -44,7 +44,6 @@ import { ErrorDataSource } from './sources/ErrorDataSource'
|
||||
import { GraphQLDataSource } from './sources/GraphQLDataSource'
|
||||
import { RemoteRequestDataSource } from './sources/RemoteRequestDataSource'
|
||||
import { resetIssuedWarnings } from '@packages/config'
|
||||
import { RemotePollingDataSource } from './sources/RemotePollingDataSource'
|
||||
|
||||
const IS_DEV_ENV = process.env.CYPRESS_INTERNAL_ENV !== 'production'
|
||||
|
||||
@@ -220,11 +219,6 @@ export class DataContext {
|
||||
return new ProjectDataSource(this)
|
||||
}
|
||||
|
||||
@cached
|
||||
get remotePolling () {
|
||||
return new RemotePollingDataSource(this)
|
||||
}
|
||||
|
||||
@cached
|
||||
get relevantRuns () {
|
||||
return new RelevantRunsDataSource(this)
|
||||
|
||||
@@ -467,6 +467,7 @@ export class GitDataSource {
|
||||
}
|
||||
|
||||
__setGitHashesForTesting (hashes: string[]) {
|
||||
debug('Setting git hashes for testing', hashes)
|
||||
this.#gitHashes = hashes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export class RelevantRunSpecsDataSource {
|
||||
debug('subscriptions', subscriptions)
|
||||
const runIds = uniq(compact(subscriptions?.map((sub) => sub.meta?.runId)))
|
||||
|
||||
debug('Polling for specs for runs: %o - runIds: %o', runIds)
|
||||
debug('Polling for specs for runs: %o', runIds)
|
||||
|
||||
const query = this.createQuery(compact(subscriptions.map((sub) => sub.meta?.info)))
|
||||
|
||||
@@ -104,6 +104,10 @@ export class RelevantRunSpecsDataSource {
|
||||
debug(`Run data is `, runs)
|
||||
|
||||
runs.forEach(async (run) => {
|
||||
if (!run) {
|
||||
return
|
||||
}
|
||||
|
||||
const cachedRun = this.#cached.get(run.id)
|
||||
|
||||
if (!cachedRun || !isEqual(run, cachedRun)) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { gql } from '@urql/core'
|
||||
import { print } from 'graphql'
|
||||
import debugLib from 'debug'
|
||||
import { isEqual, takeWhile } from 'lodash'
|
||||
import { isEqual, take, takeWhile } from 'lodash'
|
||||
|
||||
import type { DataContext } from '../DataContext'
|
||||
import type { Query, RelevantRun, RelevantRunInfo, RelevantRunLocationEnum } from '../gen/graphcache-config.gen'
|
||||
@@ -37,7 +37,7 @@ const RELEVANT_RUN_OPERATION_DOC = gql`
|
||||
|
||||
const RELEVANT_RUN_UPDATE_OPERATION = print(RELEVANT_RUN_OPERATION_DOC)
|
||||
|
||||
export const RUNS_EMPTY_RETURN: RelevantRun = { commitsAhead: -1, all: [] }
|
||||
export const RUNS_EMPTY_RETURN: RelevantRun = { commitsAhead: -1, all: [], latest: [] }
|
||||
|
||||
/**
|
||||
* DataSource to encapsulate querying Cypress Cloud for runs that match a list of local Git commit shas
|
||||
@@ -118,6 +118,7 @@ export class RelevantRunsDataSource {
|
||||
return run != null && !!run.runNumber && !!run.status && !!run.commitInfo?.sha
|
||||
}).map((run) => {
|
||||
return {
|
||||
runId: run.id,
|
||||
runNumber: run.runNumber!,
|
||||
status: run.status!,
|
||||
sha: run.commitInfo?.sha!,
|
||||
@@ -140,8 +141,9 @@ export class RelevantRunsDataSource {
|
||||
if (run) {
|
||||
//filter relevant runs in case moving causes the previously selected run to no longer be relevant
|
||||
const relevantRuns = this.#takeRelevantRuns(this.#cached.all)
|
||||
const latestRuns = this.#cached.latest
|
||||
|
||||
await this.#emitRelevantRunsIfChanged({ relevantRuns, selectedRun: run, shas })
|
||||
await this.#emitRelevantRunsIfChanged({ relevantRuns, selectedRun: run, shas, latestRuns })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +183,8 @@ export class RelevantRunsDataSource {
|
||||
|
||||
const relevantRuns: RelevantRunInfo[] = this.#takeRelevantRuns(runs)
|
||||
|
||||
const latestRuns: RelevantRunInfo[] = this.#takeLatestRuns(runs)
|
||||
|
||||
// If there is a selected run that is no longer considered relevant,
|
||||
// make sure to still add it to the list of runs
|
||||
const selectedRunNumber = selectedRun?.runNumber
|
||||
@@ -196,7 +200,7 @@ export class RelevantRunsDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
await this.#emitRelevantRunsIfChanged({ relevantRuns, selectedRun, shas })
|
||||
await this.#emitRelevantRunsIfChanged({ relevantRuns, selectedRun, shas, latestRuns })
|
||||
}
|
||||
|
||||
#takeRelevantRuns (runs: RelevantRunInfo[]) {
|
||||
@@ -210,20 +214,30 @@ export class RelevantRunsDataSource {
|
||||
return run.status === 'RUNNING' || run.sha === firstShaWithCompletedRun
|
||||
})
|
||||
|
||||
debug('runs after take', relevantRuns)
|
||||
debug('relevant runs after take', relevantRuns)
|
||||
|
||||
return relevantRuns
|
||||
}
|
||||
|
||||
async #emitRelevantRunsIfChanged ({ relevantRuns, selectedRun, shas }: {
|
||||
#takeLatestRuns (runs: RelevantRunInfo[]) {
|
||||
const latestRuns = take(runs, 10)
|
||||
|
||||
debug('latest runs after take', latestRuns)
|
||||
|
||||
return latestRuns
|
||||
}
|
||||
|
||||
async #emitRelevantRunsIfChanged ({ relevantRuns, selectedRun, shas, latestRuns }: {
|
||||
relevantRuns: RelevantRunInfo[]
|
||||
selectedRun: RelevantRunInfo | undefined
|
||||
shas: string[]
|
||||
latestRuns: RelevantRunInfo[]
|
||||
}) {
|
||||
const commitsAhead = selectedRun?.sha ? shas.indexOf(selectedRun.sha) : -1
|
||||
|
||||
const toCache: RelevantRun = {
|
||||
all: relevantRuns,
|
||||
latest: latestRuns,
|
||||
commitsAhead,
|
||||
selectedRunNumber: selectedRun?.runNumber,
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
import { gql } from '@urql/core'
|
||||
import { print } from 'graphql'
|
||||
import debugLib from 'debug'
|
||||
|
||||
import type { DataContext } from '../DataContext'
|
||||
import type { Query } from '../gen/graphcache-config.gen'
|
||||
|
||||
const debug = debugLib('cypress:data-context:sources:RemotePollingDataSource')
|
||||
|
||||
const LATEST_RUN_UPDATE_OPERATION_DOC = gql`
|
||||
query RemotePollingDataSource_latestRunUpdateSpecData(
|
||||
$commitBranch: String!
|
||||
$projectSlug: String!
|
||||
# sinceDateTime: DateTime
|
||||
) {
|
||||
cloudLatestRunUpdateSpecData(commitBranch: $commitBranch, projectSlug: $projectSlug) {
|
||||
mostRecentUpdate
|
||||
pollingInterval
|
||||
}
|
||||
}
|
||||
`
|
||||
const LATEST_RUN_UPDATE_OPERATION = print(LATEST_RUN_UPDATE_OPERATION_DOC)
|
||||
|
||||
export class RemotePollingDataSource {
|
||||
#subscribedCount = 0
|
||||
#specPolling?: NodeJS.Timeout
|
||||
constructor (private ctx: DataContext) {}
|
||||
|
||||
#startPollingForSpecs (branch: string, projectSlug: string) {
|
||||
// when the page refreshes, a previously started subscription may be running
|
||||
// this will reset it and start a new one
|
||||
if (this.#specPolling) {
|
||||
clearTimeout(this.#specPolling)
|
||||
}
|
||||
|
||||
debug(`Sending initial request for startPollingForSpecs`)
|
||||
|
||||
// Send the spec polling request
|
||||
this.#sendSpecPollingRequest(branch, projectSlug).catch((e) => {
|
||||
debug(`Error executing specPollingRequest %o`, e)
|
||||
})
|
||||
}
|
||||
|
||||
#stopPolling () {
|
||||
if (this.#specPolling) {
|
||||
clearTimeout(this.#specPolling)
|
||||
this.#specPolling = undefined
|
||||
}
|
||||
}
|
||||
|
||||
async #sendSpecPollingRequest (commitBranch: string, projectSlug: string) {
|
||||
const result = await this.ctx.cloud.executeRemoteGraphQL<Pick<Query, 'cloudLatestRunUpdateSpecData'>>({
|
||||
fieldName: 'cloudLatestRunUpdateSpecData',
|
||||
operationDoc: LATEST_RUN_UPDATE_OPERATION_DOC,
|
||||
operation: LATEST_RUN_UPDATE_OPERATION,
|
||||
operationVariables: {
|
||||
commitBranch,
|
||||
projectSlug,
|
||||
},
|
||||
requestPolicy: 'network-only', // we never want to hit local cache for this request
|
||||
})
|
||||
|
||||
debug(`%s Response for startPollingForSpecs %o`, new Date().toISOString(), result)
|
||||
|
||||
const secondsToPollNext = (result.data?.cloudLatestRunUpdateSpecData?.pollingInterval ?? 30)
|
||||
const mostRecentUpdate = result.data?.cloudLatestRunUpdateSpecData?.mostRecentUpdate ?? null
|
||||
|
||||
this.ctx.emitter.specPollingUpdate(mostRecentUpdate)
|
||||
|
||||
this.#specPolling = setTimeout(async () => {
|
||||
await this.#sendSpecPollingRequest(commitBranch, projectSlug)
|
||||
}, secondsToPollNext * 1000)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
subscribeAndPoll (branch?: string | null, projectSlug?: string | null) {
|
||||
if (!branch || !projectSlug) {
|
||||
return this.ctx.emitter.subscribeTo('noopChange', { sendInitial: false })
|
||||
}
|
||||
|
||||
debug('Subscribing, subscribed count %d', this.#subscribedCount)
|
||||
if (this.#subscribedCount === 0) {
|
||||
debug('Starting polling')
|
||||
this.#startPollingForSpecs(branch, projectSlug)
|
||||
}
|
||||
|
||||
this.#subscribedCount++
|
||||
|
||||
return this.ctx.emitter.subscribeTo('specPollingUpdate', {
|
||||
sendInitial: false,
|
||||
onUnsubscribe: () => {
|
||||
debug('Unsubscribing, subscribed count %d', this.#subscribedCount)
|
||||
this.#subscribedCount--
|
||||
if (this.#subscribedCount === 0) {
|
||||
this.#stopPolling()
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ export * from './MigrationDataSource'
|
||||
export * from './ProjectDataSource'
|
||||
export * from './RelevantRunSpecsDataSource'
|
||||
export * from './RelevantRunsDataSource'
|
||||
export * from './RemotePollingDataSource'
|
||||
export * from './RemoteRequestDataSource'
|
||||
export * from './UtilDataSource'
|
||||
export * from './VersionsDataSource'
|
||||
|
||||
@@ -17,8 +17,8 @@ type TestProject = typeof _PROJECTS[number]
|
||||
function formatRun (project: TestProject, index: number) {
|
||||
const run = project.data.cloudProjectBySlug.runsByCommitShas?.[index]
|
||||
|
||||
return (({ status, runNumber, commitInfo }) => {
|
||||
return { status, runNumber, sha: commitInfo.sha }
|
||||
return (({ status, runNumber, commitInfo, id }) => {
|
||||
return { status, runNumber, sha: commitInfo.sha, runId: id }
|
||||
})(run)
|
||||
}
|
||||
|
||||
@@ -160,6 +160,7 @@ describe('RelevantRunsDataSource', () => {
|
||||
expect(subValues[0], 'should emit first result of running').to.eql({
|
||||
all: [formatRun(FAKE_PROJECT_ONE_RUNNING_RUN, 0)],
|
||||
commitsAhead: 0,
|
||||
latest: [formatRun(FAKE_PROJECT_ONE_RUNNING_RUN, 0)],
|
||||
selectedRunNumber: 1,
|
||||
})
|
||||
|
||||
@@ -168,12 +169,20 @@ describe('RelevantRunsDataSource', () => {
|
||||
formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 0),
|
||||
formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 1),
|
||||
],
|
||||
latest: [
|
||||
formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 0),
|
||||
formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 1),
|
||||
],
|
||||
commitsAhead: 1,
|
||||
selectedRunNumber: 1,
|
||||
})
|
||||
|
||||
expect(subValues[2], 'should emit selected run after moving').to.eql({
|
||||
all: [formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 0)],
|
||||
latest: [
|
||||
formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 0),
|
||||
formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 1),
|
||||
],
|
||||
commitsAhead: 0,
|
||||
selectedRunNumber: 4,
|
||||
})
|
||||
@@ -213,12 +222,17 @@ describe('RelevantRunsDataSource', () => {
|
||||
|
||||
expect(subValues[0], 'should emit first result of running').to.eql({
|
||||
all: [formatRun(FAKE_PROJECT_ONE_RUNNING_RUN, 0)],
|
||||
latest: [formatRun(FAKE_PROJECT_ONE_RUNNING_RUN, 0)],
|
||||
commitsAhead: 0,
|
||||
selectedRunNumber: 1,
|
||||
})
|
||||
|
||||
expect(subValues[1], 'should emit newer completed run on different sha').to.eql({
|
||||
all: [formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 0)],
|
||||
latest: [
|
||||
formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 0),
|
||||
formatRun(FAKE_PROJECT_MULTIPLE_COMPLETED, 1),
|
||||
],
|
||||
commitsAhead: 0,
|
||||
selectedRunNumber: 4,
|
||||
})
|
||||
|
||||
1
packages/driver/.gitignore
vendored
1
packages/driver/.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
cypress/downloads
|
||||
|
||||
@@ -12,14 +12,18 @@ describe('src/cy/commands/exec', () => {
|
||||
cy.stub(Cypress, 'backend').log(false).callThrough()
|
||||
})
|
||||
|
||||
it('triggers \'exec\' with the right options', () => {
|
||||
it('sends privileged exec to backend with the right options', () => {
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.exec('ls').then(() => {
|
||||
expect(Cypress.backend).to.be.calledWith('exec', {
|
||||
cmd: 'ls',
|
||||
timeout: 2500,
|
||||
env: {},
|
||||
expect(Cypress.backend).to.be.calledWith('run:privileged', {
|
||||
commandName: 'exec',
|
||||
userArgs: ['ls'],
|
||||
options: {
|
||||
cmd: 'ls',
|
||||
timeout: 2500,
|
||||
env: {},
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -28,17 +32,19 @@ describe('src/cy/commands/exec', () => {
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.exec('ls', { env: { FOO: 'foo' } }).then(() => {
|
||||
expect(Cypress.backend).to.be.calledWith('exec', {
|
||||
cmd: 'ls',
|
||||
timeout: 2500,
|
||||
env: {
|
||||
FOO: 'foo',
|
||||
expect(Cypress.backend).to.be.calledWith('run:privileged', {
|
||||
commandName: 'exec',
|
||||
userArgs: ['ls', { env: { FOO: 'foo' } }],
|
||||
options: {
|
||||
cmd: 'ls',
|
||||
timeout: 2500,
|
||||
env: { FOO: 'foo' },
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('really works', () => {
|
||||
it('works e2e', () => {
|
||||
// output is trimmed
|
||||
cy.exec('echo foo', { timeout: 20000 }).its('stdout').should('eq', 'foo')
|
||||
})
|
||||
@@ -188,7 +194,7 @@ describe('src/cy/commands/exec', () => {
|
||||
})
|
||||
|
||||
it('throws when the execution errors', function (done) {
|
||||
Cypress.backend.withArgs('exec').rejects(new Error('exec failed'))
|
||||
Cypress.backend.withArgs('run:privileged').rejects(new Error('exec failed'))
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
const { lastLog } = this
|
||||
@@ -207,7 +213,7 @@ describe('src/cy/commands/exec', () => {
|
||||
})
|
||||
|
||||
it('throws after timing out', function (done) {
|
||||
Cypress.backend.withArgs('exec').resolves(Promise.delay(250))
|
||||
Cypress.backend.withArgs('run:privileged').resolves(Promise.delay(250))
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
const { lastLog } = this
|
||||
@@ -225,7 +231,7 @@ describe('src/cy/commands/exec', () => {
|
||||
})
|
||||
|
||||
it('logs once on error', function (done) {
|
||||
Cypress.backend.withArgs('exec').rejects(new Error('exec failed'))
|
||||
Cypress.backend.withArgs('run:privileged').rejects(new Error('exec failed'))
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
const { lastLog } = this
|
||||
@@ -245,7 +251,7 @@ describe('src/cy/commands/exec', () => {
|
||||
|
||||
err.timedOut = true
|
||||
|
||||
Cypress.backend.withArgs('exec').rejects(err)
|
||||
Cypress.backend.withArgs('run:privileged').rejects(err)
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
expect(err.message).to.include('`cy.exec(\'sleep 2\')` timed out after waiting `100ms`.')
|
||||
|
||||
@@ -22,48 +22,66 @@ describe('src/cy/commands/files', () => {
|
||||
cy.readFile('does-not-exist').should('be.null')
|
||||
})
|
||||
|
||||
it('triggers \'read:file\' with the right options', () => {
|
||||
Cypress.backend.withArgs('read:file').resolves(okResponse)
|
||||
it('sends privileged readFile to backend with the right options', () => {
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.readFile('foo.json').then(() => {
|
||||
expect(Cypress.backend).to.be.calledWith(
|
||||
'read:file',
|
||||
'foo.json',
|
||||
{ encoding: 'utf8' },
|
||||
'run:privileged',
|
||||
{
|
||||
commandName: 'readFile',
|
||||
userArgs: ['foo.json'],
|
||||
options: {
|
||||
file: 'foo.json',
|
||||
encoding: 'utf8',
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('can take encoding as second argument', () => {
|
||||
Cypress.backend.withArgs('read:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.readFile('foo.json', 'ascii').then(() => {
|
||||
expect(Cypress.backend).to.be.calledWith(
|
||||
'read:file',
|
||||
'foo.json',
|
||||
{ encoding: 'ascii' },
|
||||
'run:privileged',
|
||||
{
|
||||
commandName: 'readFile',
|
||||
userArgs: ['foo.json', 'ascii'],
|
||||
options: {
|
||||
file: 'foo.json',
|
||||
encoding: 'ascii',
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/1558
|
||||
it('passes explicit null encoding through to server and decodes response', () => {
|
||||
Cypress.backend.withArgs('read:file').resolves({
|
||||
Cypress.backend.resolves({
|
||||
contents: Buffer.from('\n'),
|
||||
filePath: '/path/to/foo.json',
|
||||
})
|
||||
|
||||
cy.readFile('foo.json', null).then(() => {
|
||||
expect(Cypress.backend).to.be.calledWith(
|
||||
'read:file',
|
||||
'foo.json',
|
||||
{ encoding: null },
|
||||
'run:privileged',
|
||||
{
|
||||
commandName: 'readFile',
|
||||
userArgs: ['foo.json', null],
|
||||
options: {
|
||||
file: 'foo.json',
|
||||
encoding: null,
|
||||
},
|
||||
},
|
||||
)
|
||||
}).should('eql', Buffer.from('\n'))
|
||||
})
|
||||
|
||||
it('sets the contents as the subject', () => {
|
||||
Cypress.backend.withArgs('read:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.readFile('foo.json').then((subject) => {
|
||||
expect(subject).to.equal('contents')
|
||||
@@ -81,7 +99,7 @@ describe('src/cy/commands/files', () => {
|
||||
retries += 1
|
||||
})
|
||||
|
||||
Cypress.backend.withArgs('read:file')
|
||||
Cypress.backend.withArgs('run:privileged')
|
||||
.onFirstCall()
|
||||
.rejects(err)
|
||||
.onSecondCall()
|
||||
@@ -99,7 +117,7 @@ describe('src/cy/commands/files', () => {
|
||||
retries += 1
|
||||
})
|
||||
|
||||
Cypress.backend.withArgs('read:file')
|
||||
Cypress.backend.withArgs('run:privileged')
|
||||
.onFirstCall()
|
||||
.resolves({
|
||||
contents: 'foobarbaz',
|
||||
@@ -129,7 +147,7 @@ describe('src/cy/commands/files', () => {
|
||||
})
|
||||
|
||||
it('can turn off logging', () => {
|
||||
Cypress.backend.withArgs('read:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.readFile('foo.json', { log: false }).then(function () {
|
||||
const logs = _.filter(this.logs, (log) => {
|
||||
@@ -141,7 +159,7 @@ describe('src/cy/commands/files', () => {
|
||||
})
|
||||
|
||||
it('logs immediately before resolving', function () {
|
||||
Cypress.backend.withArgs('read:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.on('log:added', (attrs, log) => {
|
||||
if (attrs.name === 'readFile') {
|
||||
@@ -236,7 +254,7 @@ describe('src/cy/commands/files', () => {
|
||||
err.code = 'EISDIR'
|
||||
err.filePath = '/path/to/foo'
|
||||
|
||||
Cypress.backend.withArgs('read:file').rejects(err)
|
||||
Cypress.backend.withArgs('run:privileged').rejects(err)
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
const { fileLog } = this
|
||||
@@ -268,7 +286,7 @@ describe('src/cy/commands/files', () => {
|
||||
err.code = 'ENOENT'
|
||||
err.filePath = '/path/to/foo.json'
|
||||
|
||||
Cypress.backend.withArgs('read:file').rejects(err)
|
||||
Cypress.backend.withArgs('run:privileged').rejects(err)
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
const { fileLog } = this
|
||||
@@ -297,7 +315,7 @@ describe('src/cy/commands/files', () => {
|
||||
err.code = 'ENOENT'
|
||||
err.filePath = '/path/to/foo.json'
|
||||
|
||||
Cypress.backend.withArgs('read:file').rejects(err)
|
||||
Cypress.backend.withArgs('run:privileged').rejects(err)
|
||||
let hasRetried = false
|
||||
|
||||
cy.on('command:retry', () => {
|
||||
@@ -326,7 +344,7 @@ describe('src/cy/commands/files', () => {
|
||||
})
|
||||
|
||||
it('throws a specific error when file exists when it shouldn\'t', function (done) {
|
||||
Cypress.backend.withArgs('read:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
const { fileLog, logs } = this
|
||||
@@ -353,7 +371,7 @@ describe('src/cy/commands/files', () => {
|
||||
})
|
||||
|
||||
it('passes through assertion error when not about existence', function (done) {
|
||||
Cypress.backend.withArgs('read:file').resolves({
|
||||
Cypress.backend.resolves({
|
||||
contents: 'foo',
|
||||
})
|
||||
|
||||
@@ -376,7 +394,7 @@ describe('src/cy/commands/files', () => {
|
||||
})
|
||||
|
||||
it('throws when the read timeout expires', function (done) {
|
||||
Cypress.backend.withArgs('read:file').callsFake(() => {
|
||||
Cypress.backend.withArgs('run:privileged').callsFake(() => {
|
||||
return new Cypress.Promise(() => { /* Broken promise for timeout */ })
|
||||
})
|
||||
|
||||
@@ -400,7 +418,7 @@ describe('src/cy/commands/files', () => {
|
||||
it('uses defaultCommandTimeout config value if option not provided', {
|
||||
defaultCommandTimeout: 42,
|
||||
}, function (done) {
|
||||
Cypress.backend.withArgs('read:file').callsFake(() => {
|
||||
Cypress.backend.withArgs('run:privileged').callsFake(() => {
|
||||
return new Cypress.Promise(() => { /* Broken promise for timeout */ })
|
||||
})
|
||||
|
||||
@@ -424,33 +442,41 @@ describe('src/cy/commands/files', () => {
|
||||
})
|
||||
|
||||
describe('#writeFile', () => {
|
||||
it('triggers \'write:file\' with the right options', () => {
|
||||
Cypress.backend.withArgs('write:file').resolves(okResponse)
|
||||
it('sends privileged writeFile to backend with the right options', () => {
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.writeFile('foo.txt', 'contents').then(() => {
|
||||
expect(Cypress.backend).to.be.calledWith(
|
||||
'write:file',
|
||||
'foo.txt',
|
||||
'contents',
|
||||
'run:privileged',
|
||||
{
|
||||
encoding: 'utf8',
|
||||
flag: 'w',
|
||||
commandName: 'writeFile',
|
||||
userArgs: ['foo.txt', 'contents'],
|
||||
options: {
|
||||
fileName: 'foo.txt',
|
||||
contents: 'contents',
|
||||
encoding: 'utf8',
|
||||
flag: 'w',
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('can take encoding as third argument', () => {
|
||||
Cypress.backend.withArgs('write:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.writeFile('foo.txt', 'contents', 'ascii').then(() => {
|
||||
expect(Cypress.backend).to.be.calledWith(
|
||||
'write:file',
|
||||
'foo.txt',
|
||||
'contents',
|
||||
'run:privileged',
|
||||
{
|
||||
encoding: 'ascii',
|
||||
flag: 'w',
|
||||
commandName: 'writeFile',
|
||||
userArgs: ['foo.txt', 'contents', 'ascii'],
|
||||
options: {
|
||||
fileName: 'foo.txt',
|
||||
contents: 'contents',
|
||||
encoding: 'ascii',
|
||||
flag: 'w',
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
@@ -458,39 +484,49 @@ describe('src/cy/commands/files', () => {
|
||||
|
||||
// https://github.com/cypress-io/cypress/issues/1558
|
||||
it('explicit null encoding is sent to server as Buffer', () => {
|
||||
Cypress.backend.withArgs('write:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.writeFile('foo.txt', Buffer.from([0, 0, 54, 255]), null).then(() => {
|
||||
const buffer = Buffer.from([0, 0, 54, 255])
|
||||
|
||||
cy.writeFile('foo.txt', buffer, null).then(() => {
|
||||
expect(Cypress.backend).to.be.calledWith(
|
||||
'write:file',
|
||||
'foo.txt',
|
||||
Buffer.from([0, 0, 54, 255]),
|
||||
'run:privileged',
|
||||
{
|
||||
encoding: null,
|
||||
flag: 'w',
|
||||
commandName: 'writeFile',
|
||||
userArgs: ['foo.txt', buffer, null],
|
||||
options: {
|
||||
fileName: 'foo.txt',
|
||||
contents: buffer,
|
||||
encoding: null,
|
||||
flag: 'w',
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('can take encoding as part of options', () => {
|
||||
Cypress.backend.withArgs('write:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.writeFile('foo.txt', 'contents', { encoding: 'ascii' }).then(() => {
|
||||
expect(Cypress.backend).to.be.calledWith(
|
||||
'write:file',
|
||||
'foo.txt',
|
||||
'contents',
|
||||
'run:privileged',
|
||||
{
|
||||
encoding: 'ascii',
|
||||
flag: 'w',
|
||||
commandName: 'writeFile',
|
||||
userArgs: ['foo.txt', 'contents', { encoding: 'ascii' }],
|
||||
options: {
|
||||
fileName: 'foo.txt',
|
||||
contents: 'contents',
|
||||
encoding: 'ascii',
|
||||
flag: 'w',
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('yields null', () => {
|
||||
Cypress.backend.withArgs('write:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.writeFile('foo.txt', 'contents').then((subject) => {
|
||||
expect(subject).to.eq(null)
|
||||
@@ -498,19 +534,19 @@ describe('src/cy/commands/files', () => {
|
||||
})
|
||||
|
||||
it('can write a string', () => {
|
||||
Cypress.backend.withArgs('write:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.writeFile('foo.txt', 'contents')
|
||||
})
|
||||
|
||||
it('can write an array as json', () => {
|
||||
Cypress.backend.withArgs('write:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.writeFile('foo.json', [])
|
||||
})
|
||||
|
||||
it('can write an object as json', () => {
|
||||
Cypress.backend.withArgs('write:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.writeFile('foo.json', {})
|
||||
})
|
||||
@@ -525,16 +561,20 @@ describe('src/cy/commands/files', () => {
|
||||
|
||||
describe('.flag', () => {
|
||||
it('sends a flag if specified', () => {
|
||||
Cypress.backend.withArgs('write:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.writeFile('foo.txt', 'contents', { flag: 'a+' }).then(() => {
|
||||
expect(Cypress.backend).to.be.calledWith(
|
||||
'write:file',
|
||||
'foo.txt',
|
||||
'contents',
|
||||
'run:privileged',
|
||||
{
|
||||
encoding: 'utf8',
|
||||
flag: 'a+',
|
||||
commandName: 'writeFile',
|
||||
userArgs: ['foo.txt', 'contents', { flag: 'a+' }],
|
||||
options: {
|
||||
fileName: 'foo.txt',
|
||||
contents: 'contents',
|
||||
encoding: 'utf8',
|
||||
flag: 'a+',
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
@@ -562,7 +602,7 @@ describe('src/cy/commands/files', () => {
|
||||
})
|
||||
|
||||
it('can turn off logging', () => {
|
||||
Cypress.backend.withArgs('write:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.writeFile('foo.txt', 'contents', { log: false }).then(function () {
|
||||
const logs = _.filter(this.logs, (log) => {
|
||||
@@ -574,7 +614,7 @@ describe('src/cy/commands/files', () => {
|
||||
})
|
||||
|
||||
it('logs immediately before resolving', function () {
|
||||
Cypress.backend.withArgs('write:file').resolves(okResponse)
|
||||
Cypress.backend.resolves(okResponse)
|
||||
|
||||
cy.on('log:added', (attrs, log) => {
|
||||
if (attrs.name === 'writeFile') {
|
||||
@@ -677,7 +717,7 @@ describe('src/cy/commands/files', () => {
|
||||
err.code = 'WHOKNOWS'
|
||||
err.filePath = '/path/to/foo.txt'
|
||||
|
||||
Cypress.backend.withArgs('write:file').rejects(err)
|
||||
Cypress.backend.withArgs('run:privileged').rejects(err)
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
const { lastLog } = this
|
||||
@@ -703,7 +743,7 @@ describe('src/cy/commands/files', () => {
|
||||
})
|
||||
|
||||
it('throws when the write timeout expires', function (done) {
|
||||
Cypress.backend.withArgs('write:file').callsFake(() => {
|
||||
Cypress.backend.withArgs('run:privileged').callsFake(() => {
|
||||
return new Cypress.Promise(() => {})
|
||||
})
|
||||
|
||||
@@ -728,7 +768,7 @@ describe('src/cy/commands/files', () => {
|
||||
it('uses defaultCommandTimeout config value if option not provided', {
|
||||
defaultCommandTimeout: 42,
|
||||
}, function (done) {
|
||||
Cypress.backend.withArgs('write:file').callsFake(() => {
|
||||
Cypress.backend.withArgs('run:privileged').callsFake(() => {
|
||||
return new Cypress.Promise(() => { /* Broken promise for timeout */ })
|
||||
})
|
||||
|
||||
|
||||
@@ -9,14 +9,18 @@ describe('src/cy/commands/task', () => {
|
||||
cy.stub(Cypress, 'backend').log(false).callThrough()
|
||||
})
|
||||
|
||||
it('calls Cypress.backend(\'task\') with the right options', () => {
|
||||
it('sends privileged task to backend with the right options', () => {
|
||||
Cypress.backend.resolves(null)
|
||||
|
||||
cy.task('foo').then(() => {
|
||||
expect(Cypress.backend).to.be.calledWith('task', {
|
||||
task: 'foo',
|
||||
timeout: 2500,
|
||||
arg: undefined,
|
||||
expect(Cypress.backend).to.be.calledWith('run:privileged', {
|
||||
commandName: 'task',
|
||||
userArgs: ['foo'],
|
||||
options: {
|
||||
task: 'foo',
|
||||
timeout: 2500,
|
||||
arg: undefined,
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -25,11 +29,13 @@ describe('src/cy/commands/task', () => {
|
||||
Cypress.backend.resolves(null)
|
||||
|
||||
cy.task('foo', { foo: 'foo' }).then(() => {
|
||||
expect(Cypress.backend).to.be.calledWith('task', {
|
||||
task: 'foo',
|
||||
timeout: 2500,
|
||||
arg: {
|
||||
foo: 'foo',
|
||||
expect(Cypress.backend).to.be.calledWith('run:privileged', {
|
||||
commandName: 'task',
|
||||
userArgs: ['foo', { foo: 'foo' }],
|
||||
options: {
|
||||
task: 'foo',
|
||||
timeout: 2500,
|
||||
arg: { foo: 'foo' },
|
||||
},
|
||||
})
|
||||
})
|
||||
@@ -184,7 +190,7 @@ describe('src/cy/commands/task', () => {
|
||||
})
|
||||
|
||||
it('throws when the task errors', function (done) {
|
||||
Cypress.backend.withArgs('task').rejects(new Error('task failed'))
|
||||
Cypress.backend.withArgs('run:privileged').rejects(new Error('task failed'))
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
const { lastLog } = this
|
||||
@@ -219,7 +225,7 @@ describe('src/cy/commands/task', () => {
|
||||
})
|
||||
|
||||
it('throws after timing out', function (done) {
|
||||
Cypress.backend.withArgs('task').resolves(Promise.delay(250))
|
||||
Cypress.backend.withArgs('run:privileged').resolves(Promise.delay(250))
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
const { lastLog } = this
|
||||
@@ -237,7 +243,7 @@ describe('src/cy/commands/task', () => {
|
||||
})
|
||||
|
||||
it('logs once on error', function (done) {
|
||||
Cypress.backend.withArgs('task').rejects(new Error('task failed'))
|
||||
Cypress.backend.withArgs('run:privileged').rejects(new Error('task failed'))
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
const { lastLog } = this
|
||||
@@ -257,7 +263,7 @@ describe('src/cy/commands/task', () => {
|
||||
|
||||
err.timedOut = true
|
||||
|
||||
Cypress.backend.withArgs('task').rejects(err)
|
||||
Cypress.backend.withArgs('run:privileged').rejects(err)
|
||||
|
||||
cy.on('fail', (err) => {
|
||||
expect(err.message).to.include('`cy.task(\'wait\')` timed out after waiting `100ms`.')
|
||||
|
||||
@@ -22,8 +22,13 @@ describe('src/cypress/script_utils', () => {
|
||||
cy.stub($sourceMapUtils, 'initializeSourceMapConsumer').resolves()
|
||||
})
|
||||
|
||||
it('fetches each script', () => {
|
||||
return $scriptUtils.runScripts(scriptWindow, scripts)
|
||||
it('fetches each script in non-webkit browsers', () => {
|
||||
return $scriptUtils.runScripts({
|
||||
browser: { family: 'chromium' },
|
||||
scripts,
|
||||
specWindow: scriptWindow,
|
||||
testingType: 'e2e',
|
||||
})
|
||||
.then(() => {
|
||||
expect($networkUtils.fetch).to.be.calledTwice
|
||||
expect($networkUtils.fetch).to.be.calledWith(scripts[0].relativeUrl)
|
||||
@@ -31,8 +36,62 @@ describe('src/cypress/script_utils', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('appends each script in e2e webkit', async () => {
|
||||
const foundScript = {
|
||||
after: cy.stub(),
|
||||
}
|
||||
const createdScript1 = {
|
||||
addEventListener: cy.stub(),
|
||||
}
|
||||
const createdScript2 = {
|
||||
addEventListener: cy.stub(),
|
||||
}
|
||||
const doc = {
|
||||
querySelector: cy.stub().returns(foundScript),
|
||||
createElement: cy.stub(),
|
||||
}
|
||||
|
||||
doc.createElement.onCall(0).returns(createdScript1)
|
||||
doc.createElement.onCall(1).returns(createdScript2)
|
||||
|
||||
scriptWindow.document = doc
|
||||
|
||||
const runScripts = $scriptUtils.runScripts({
|
||||
scripts,
|
||||
specWindow: scriptWindow,
|
||||
browser: { family: 'webkit' },
|
||||
testingType: 'e2e',
|
||||
})
|
||||
|
||||
// each script is appended and run before the next
|
||||
|
||||
await Promise.delay(1) // wait a tick due to promise
|
||||
expect(createdScript1.addEventListener).to.be.calledWith('load')
|
||||
createdScript1.addEventListener.lastCall.args[1]()
|
||||
|
||||
await Promise.delay(1) // wait a tick due to promise
|
||||
expect(createdScript2.addEventListener).to.be.calledWith('load')
|
||||
createdScript2.addEventListener.lastCall.args[1]()
|
||||
|
||||
await runScripts
|
||||
|
||||
// sets script src
|
||||
expect(createdScript1.src).to.equal(scripts[0].relativeUrl)
|
||||
expect(createdScript2.src).to.equal(scripts[1].relativeUrl)
|
||||
|
||||
// appends scripts
|
||||
expect(foundScript.after).to.be.calledTwice
|
||||
expect(foundScript.after).to.be.calledWith(createdScript1)
|
||||
expect(foundScript.after).to.be.calledWith(createdScript2)
|
||||
})
|
||||
|
||||
it('extracts the source map from each script', () => {
|
||||
return $scriptUtils.runScripts(scriptWindow, scripts)
|
||||
return $scriptUtils.runScripts({
|
||||
browser: { family: 'chromium' },
|
||||
scripts,
|
||||
specWindow: scriptWindow,
|
||||
testingType: 'e2e',
|
||||
})
|
||||
.then(() => {
|
||||
expect($sourceMapUtils.extractSourceMap).to.be.calledTwice
|
||||
expect($sourceMapUtils.extractSourceMap).to.be.calledWith('the script contents')
|
||||
@@ -41,7 +100,12 @@ describe('src/cypress/script_utils', () => {
|
||||
})
|
||||
|
||||
it('evals each script', () => {
|
||||
return $scriptUtils.runScripts(scriptWindow, scripts)
|
||||
return $scriptUtils.runScripts({
|
||||
browser: { family: 'chromium' },
|
||||
scripts,
|
||||
specWindow: scriptWindow,
|
||||
testingType: 'e2e',
|
||||
})
|
||||
.then(() => {
|
||||
expect(scriptWindow.eval).to.be.calledTwice
|
||||
expect(scriptWindow.eval).to.be.calledWith('the script contents\n//# sourceURL=http://localhost:3500cypress/integration/script1.js')
|
||||
@@ -53,7 +117,12 @@ describe('src/cypress/script_utils', () => {
|
||||
context('#runPromises', () => {
|
||||
it('handles promises and doesnt try to fetch + eval manually', async () => {
|
||||
const scriptsAsPromises = [() => Promise.resolve(), () => Promise.resolve()]
|
||||
const result = await $scriptUtils.runScripts({}, scriptsAsPromises)
|
||||
const result = await $scriptUtils.runScripts({
|
||||
browser: { family: 'chromium' },
|
||||
scripts: scriptsAsPromises,
|
||||
specWindow: {},
|
||||
testingType: 'e2e',
|
||||
})
|
||||
|
||||
expect(result).to.have.length(scriptsAsPromises.length)
|
||||
})
|
||||
|
||||
30
packages/driver/cypress/e2e/e2e/csp_headers.cy.js
Normal file
30
packages/driver/cypress/e2e/e2e/csp_headers.cy.js
Normal file
@@ -0,0 +1,30 @@
|
||||
describe('csp-headers', () => {
|
||||
it('content-security-policy headers are always stripped', () => {
|
||||
const route = '/fixtures/empty.html'
|
||||
|
||||
cy.intercept(route, (req) => {
|
||||
req.continue((res) => {
|
||||
res.headers['content-security-policy'] = `script-src http://not-here.net;`
|
||||
})
|
||||
})
|
||||
|
||||
cy.visit(route)
|
||||
.wait(1000)
|
||||
|
||||
// Next verify that inline scripts are allowed, because if they aren't, the CSP header is not getting stripped
|
||||
const inlineId = `__${Math.random()}`
|
||||
|
||||
cy.window().then((win) => {
|
||||
expect(() => {
|
||||
return win.eval(`
|
||||
var script = document.createElement('script');
|
||||
script.textContent = "window['${inlineId}'] = '${inlineId}'";
|
||||
document.head.appendChild(script);
|
||||
`)
|
||||
}).not.to.throw() // CSP should be stripped, so this should not throw
|
||||
|
||||
// Inline script should have created the var
|
||||
expect(win[`${inlineId}`]).to.equal(`${inlineId}`, 'CSP Headers are being stripped')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -35,12 +35,16 @@ context('cy.origin files', { browser: '!webkit' }, () => {
|
||||
|
||||
cy.writeFile('foo.json', contents).then(() => {
|
||||
expect(Cypress.backend).to.be.calledWith(
|
||||
'write:file',
|
||||
'foo.json',
|
||||
contents,
|
||||
'run:privileged',
|
||||
{
|
||||
encoding: 'utf8',
|
||||
flag: 'w',
|
||||
commandName: 'writeFile',
|
||||
userArgs: ['foo.json', contents],
|
||||
options: {
|
||||
fileName: 'foo.json',
|
||||
contents,
|
||||
encoding: 'utf8',
|
||||
flag: 'w',
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
@@ -208,7 +208,7 @@ context('cy.origin misc', { browser: '!webkit' }, () => {
|
||||
|
||||
it('verifies number of cy commands', () => {
|
||||
// remove custom commands we added for our own testing
|
||||
const customCommands = ['getAll', 'shouldWithTimeout', 'originLoadUtils']
|
||||
const customCommands = ['getAll', 'shouldWithTimeout', 'originLoadUtils', 'runSupportFileCustomPrivilegedCommands']
|
||||
// @ts-ignore
|
||||
const actualCommands = Cypress._.pullAll([...Object.keys(cy.commandFns), ...Object.keys(cy.queryFns)], customCommands)
|
||||
const expectedCommands = [
|
||||
|
||||
@@ -74,7 +74,7 @@ context('cy.origin waiting', { browser: '!webkit' }, () => {
|
||||
cy.intercept('/foo', (req) => {
|
||||
// delay the response to ensure the wait will wait for response
|
||||
req.reply({
|
||||
delay: 100,
|
||||
delay: 200,
|
||||
body: response,
|
||||
})
|
||||
}).as('foo')
|
||||
|
||||
@@ -7,7 +7,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a localhost domain name', () => {
|
||||
cy.origin('localhost', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://localhost/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://localhost/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://localhost') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -17,7 +17,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on an ip address', () => {
|
||||
cy.origin('127.0.0.1', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://127.0.0.1/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://127.0.0.1/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://127.0.0.1') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -29,7 +29,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it.skip('succeeds on an ipv6 address', () => {
|
||||
cy.origin('0000:0000:0000:0000:0000:0000:0000:0001', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://[::1]/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://[::1]/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://[::1]') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -39,7 +39,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a unicode domain', () => {
|
||||
cy.origin('はじめよう.みんな', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://xn--p8j9a0d9c9a.xn--q9jyb4c/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://xn--p8j9a0d9c9a.xn--q9jyb4c/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://xn--p8j9a0d9c9a.xn--q9jyb4c') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -49,7 +49,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a complete origin', () => {
|
||||
cy.origin('http://foobar1.com:3500', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `http://foobar1.com:3500/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `http://foobar1.com:3500/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://foobar1.com:3500') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -59,7 +59,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a complete origin using https', () => {
|
||||
cy.origin('https://www.foobar2.com:3500', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://www.foobar2.com:3500/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://www.foobar2.com:3500/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://www.foobar2.com:3500') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -69,7 +69,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a hostname and port', () => {
|
||||
cy.origin('foobar3.com:3500', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://foobar3.com:3500/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://foobar3.com:3500/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar3.com:3500') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -79,7 +79,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a protocol and hostname', () => {
|
||||
cy.origin('http://foobar4.com', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `http://foobar4.com/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `http://foobar4.com/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://foobar4.com') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -89,7 +89,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a subdomain', () => {
|
||||
cy.origin('app.foobar5.com', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://app.foobar5.com/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://app.foobar5.com/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://app.foobar5.com') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -99,7 +99,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds when only domain is passed', () => {
|
||||
cy.origin('foobar6.com', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://foobar6.com/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://foobar6.com/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar6.com') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -109,7 +109,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a url with path', () => {
|
||||
cy.origin('http://www.foobar7.com/login', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `http://www.foobar7.com/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `http://www.foobar7.com/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://www.foobar7.com') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -119,7 +119,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a url with a hash', () => {
|
||||
cy.origin('http://www.foobar8.com/#hash', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `http://www.foobar8.com/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `http://www.foobar8.com/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://www.foobar8.com') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -129,7 +129,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a url with a path and hash', () => {
|
||||
cy.origin('http://www.foobar9.com/login/#hash', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `http://www.foobar9.com/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `http://www.foobar9.com/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ http://www.foobar9.com') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -139,7 +139,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a domain with path', () => {
|
||||
cy.origin('foobar10.com/login', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://foobar10.com/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://foobar10.com/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar10.com') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -149,7 +149,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a domain with a hash', () => {
|
||||
cy.origin('foobar11.com/#hash', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://foobar11.com/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://foobar11.com/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar11.com') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -159,7 +159,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a domain with a path and hash', () => {
|
||||
cy.origin('foobar12.com/login/#hash', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://foobar12.com/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://foobar12.com/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://foobar12.com') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -169,7 +169,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a public suffix with a subdomain', () => {
|
||||
cy.origin('app.foobar.herokuapp.com', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://app.foobar.herokuapp.com/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://app.foobar.herokuapp.com/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://app.foobar.herokuapp.com') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -179,7 +179,7 @@ describe('cy.origin', { browser: '!webkit' }, () => {
|
||||
it('succeeds on a machine name', () => {
|
||||
cy.origin('machine-name', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://machine-name/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://machine-name/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://machine-name') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -356,7 +356,7 @@ describe('cy.origin - external hosts', { browser: '!webkit' }, () => {
|
||||
cy.visit('https://www.foobar.com:3502/fixtures/primary-origin.html')
|
||||
cy.origin('https://www.idp.com:3502', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://www.idp.com:3502/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://www.idp.com:3502/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://www.idp.com:3502') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
@@ -372,7 +372,7 @@ describe('cy.origin - external hosts', { browser: '!webkit' }, () => {
|
||||
cy.visit('https://www.google.com')
|
||||
cy.origin('accounts.google.com', () => undefined)
|
||||
cy.then(() => {
|
||||
const expectedSrc = `https://accounts.google.com/__cypress/spec-bridge-iframes`
|
||||
const expectedSrc = `https://accounts.google.com/__cypress/spec-bridge-iframes?browserFamily=${Cypress.browser.family}`
|
||||
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://accounts.google.com') as HTMLIFrameElement
|
||||
|
||||
expect(iframe.src).to.equal(expectedSrc)
|
||||
|
||||
191
packages/driver/cypress/e2e/e2e/privileged_commands.cy.ts
Normal file
191
packages/driver/cypress/e2e/e2e/privileged_commands.cy.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import { runImportedPrivilegedCommands } from '../../support/utils'
|
||||
|
||||
const isWebkit = Cypress.isBrowser({ family: 'webkit' })
|
||||
|
||||
function runSpecFunctionCommands () {
|
||||
cy.exec('echo "hello"')
|
||||
cy.readFile('cypress/fixtures/app.json')
|
||||
cy.writeFile('cypress/_test-output/written.json', 'contents')
|
||||
cy.task('return:arg', 'arg')
|
||||
cy.get('#basic').selectFile('cypress/fixtures/valid.json')
|
||||
if (!isWebkit) {
|
||||
cy.origin('http://foobar.com:3500', () => {})
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add('runSpecFileCustomPrivilegedCommands', runSpecFunctionCommands)
|
||||
|
||||
describe('privileged commands', () => {
|
||||
describe('in spec file or support file', () => {
|
||||
let ranInBeforeEach = false
|
||||
|
||||
beforeEach(() => {
|
||||
if (ranInBeforeEach) return
|
||||
|
||||
ranInBeforeEach = true
|
||||
|
||||
// ensures these run properly in hooks, but only run it once per spec run
|
||||
cy.exec('echo "hello"')
|
||||
cy.readFile('cypress/fixtures/app.json')
|
||||
cy.writeFile('cypress/_test-output/written.json', 'contents')
|
||||
cy.task('return:arg', 'arg')
|
||||
cy.get('#basic').selectFile('cypress/fixtures/valid.json')
|
||||
if (!isWebkit) {
|
||||
cy.origin('http://foobar.com:3500', () => {})
|
||||
}
|
||||
})
|
||||
|
||||
it('passes in test body', () => {
|
||||
cy.exec('echo "hello"')
|
||||
cy.readFile('cypress/fixtures/app.json')
|
||||
cy.writeFile('cypress/_test-output/written.json', 'contents')
|
||||
cy.task('return:arg', 'arg')
|
||||
cy.get('#basic').selectFile('cypress/fixtures/valid.json')
|
||||
if (!isWebkit) {
|
||||
cy.origin('http://foobar.com:3500', () => {})
|
||||
}
|
||||
})
|
||||
|
||||
it('passes two or more exact commands in a row', () => {
|
||||
cy.task('return:arg', 'arg')
|
||||
cy.task('return:arg', 'arg')
|
||||
})
|
||||
|
||||
it('passes in test body .then() callback', () => {
|
||||
cy.then(() => {
|
||||
cy.exec('echo "hello"')
|
||||
cy.readFile('cypress/fixtures/app.json')
|
||||
cy.writeFile('cypress/_test-output/written.json', 'contents')
|
||||
cy.task('return:arg', 'arg')
|
||||
cy.get('#basic').selectFile('cypress/fixtures/valid.json')
|
||||
if (!isWebkit) {
|
||||
cy.origin('http://foobar.com:3500', () => {})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('passes in spec function', () => {
|
||||
runSpecFunctionCommands()
|
||||
})
|
||||
|
||||
it('passes in imported function', () => {
|
||||
runImportedPrivilegedCommands()
|
||||
})
|
||||
|
||||
it('passes in support file global function', () => {
|
||||
window.runGlobalPrivilegedCommands()
|
||||
})
|
||||
|
||||
it('passes in spec file custom command', () => {
|
||||
cy.runSpecFileCustomPrivilegedCommands()
|
||||
})
|
||||
|
||||
it('passes in support file custom command', () => {
|
||||
cy.runSupportFileCustomPrivilegedCommands()
|
||||
})
|
||||
|
||||
// cy.origin() doesn't currently have webkit support
|
||||
it('passes in .origin() callback', { browser: '!webkit' }, () => {
|
||||
cy.origin('http://foobar.com:3500', () => {
|
||||
cy.exec('echo "hello"')
|
||||
cy.readFile('cypress/fixtures/app.json')
|
||||
cy.writeFile('cypress/_test-output/written.json', 'contents')
|
||||
cy.task('return:arg', 'arg')
|
||||
|
||||
// there's a bug using cy.selectFile() with a path inside of
|
||||
// cy.origin(): https://github.com/cypress-io/cypress/issues/25261
|
||||
// cy.visit('/fixtures/files-form.html')
|
||||
// cy.get('#basic').selectFile('cypress/fixtures/valid.json')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('in AUT', () => {
|
||||
const strategies = ['inline', 'then', 'eval', 'function']
|
||||
const commands = ['exec', 'readFile', 'writeFile', 'selectFile', 'task']
|
||||
|
||||
// cy.origin() doesn't currently have webkit support
|
||||
if (!Cypress.isBrowser({ family: 'webkit' })) {
|
||||
commands.push('origin')
|
||||
}
|
||||
|
||||
const errorForCommand = (commandName) => {
|
||||
return `\`cy.${commandName}()\` must only be invoked from the spec file or support file.`
|
||||
}
|
||||
|
||||
strategies.forEach((strategy) => {
|
||||
commands.forEach((command) => {
|
||||
describe(`strategy: ${strategy}`, () => {
|
||||
describe(`command: ${command}`, () => {
|
||||
it('fails in html script', (done) => {
|
||||
cy.on('fail', (err) => {
|
||||
expect(err.message).to.include(errorForCommand(command))
|
||||
done()
|
||||
})
|
||||
|
||||
cy.visit(`/aut-commands?strategy=${strategy}&command=${command}`)
|
||||
})
|
||||
|
||||
it('fails in separate script', (done) => {
|
||||
cy.on('fail', (err) => {
|
||||
expect(err.message).to.include(errorForCommand(command))
|
||||
done()
|
||||
})
|
||||
|
||||
cy.visit(`/fixtures/aut-commands.html?strategy=${strategy}&command=${command}`)
|
||||
})
|
||||
|
||||
it('does not run command in separate script appended to spec frame', () => {
|
||||
let ranCommand = false
|
||||
|
||||
cy.on('log:added', (attrs) => {
|
||||
if (attrs.name === command) {
|
||||
ranCommand = true
|
||||
}
|
||||
})
|
||||
|
||||
// this attempts to run the command by appending a <script> to the
|
||||
// spec frame, but the Content-Security-Policy we set will prevent
|
||||
// that script from running
|
||||
cy.visit(`/aut-commands?appendToSpecFrame=true&strategy=${strategy}&command=${command}`)
|
||||
// wait 500ms then ensure the command did not run
|
||||
cy.wait(500).then(() => {
|
||||
expect(ranCommand, `expected cy.${command}() not to run, but it did`).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
// while not immediately obvious, this basically triggers using
|
||||
// cy.origin() within itself. that doesn't work anyways and
|
||||
// hits a different error, so it can't be used outside of the spec
|
||||
// in this manner
|
||||
if (command !== 'origin') {
|
||||
// cy.origin() doesn't currently have webkit support
|
||||
it('fails in cross-origin html script', { browser: '!webkit' }, (done) => {
|
||||
cy.on('fail', (err) => {
|
||||
expect(err.message).to.include(errorForCommand(command))
|
||||
done()
|
||||
})
|
||||
|
||||
cy.origin('http://foobar.com:3500', { args: { strategy, command } }, ({ strategy, command }) => {
|
||||
cy.visit(`/aut-commands?strategy=${strategy}&command=${command}`)
|
||||
})
|
||||
})
|
||||
|
||||
// cy.origin() doesn't currently have webkit support
|
||||
it('fails in cross-origin separate script', { browser: '!webkit' }, (done) => {
|
||||
cy.on('fail', (err) => {
|
||||
expect(err.message).to.include(errorForCommand(command))
|
||||
done()
|
||||
})
|
||||
|
||||
cy.origin('http://foobar.com:3500', { args: { strategy, command } }, ({ strategy, command }) => {
|
||||
cy.visit(`/fixtures/aut-commands.html?strategy=${strategy}&command=${command}`)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
2
packages/driver/cypress/fixtures/aut-commands.html
Normal file
2
packages/driver/cypress/fixtures/aut-commands.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<input type="file" />
|
||||
<script src="aut-commands.js"></script>
|
||||
97
packages/driver/cypress/fixtures/aut-commands.js
Normal file
97
packages/driver/cypress/fixtures/aut-commands.js
Normal file
@@ -0,0 +1,97 @@
|
||||
(() => {
|
||||
const urlParams = new URLSearchParams(window.__search || window.location.search)
|
||||
const appendToSpecFrame = !!urlParams.get('appendToSpecFrame')
|
||||
const strategy = urlParams.get('strategy')
|
||||
const command = urlParams.get('command')
|
||||
const cy = window.Cypress.cy
|
||||
|
||||
if (cy.state('current')) {
|
||||
cy.state('current').attributes.args = [() => {}]
|
||||
}
|
||||
|
||||
const TOP = 'top' // prevents frame-busting
|
||||
// recursively tries sibling frames until finding the spec frame, which
|
||||
// should be the first same-origin one we come across
|
||||
const specFrame = window.__isSpecFrame ? window : (() => {
|
||||
const tryFrame = (index) => {
|
||||
try {
|
||||
// will throw if cross-origin
|
||||
window[TOP].frames[index].location.href
|
||||
|
||||
return window[TOP].frames[index]
|
||||
} catch (err) {
|
||||
return tryFrame(index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
return tryFrame(1)
|
||||
})()
|
||||
|
||||
const run = () => {
|
||||
switch (command) {
|
||||
case 'exec':
|
||||
cy.exec('echo "Goodbye"')
|
||||
break
|
||||
case 'readFile':
|
||||
cy.readFile('cypress/fixtures/example.json')
|
||||
break
|
||||
case 'writeFile':
|
||||
cy.writeFile('cypress/_test-output/written.json', 'other contents')
|
||||
break
|
||||
case 'task':
|
||||
cy.task('return:arg', 'other arg')
|
||||
break
|
||||
case 'selectFile':
|
||||
cy.get('input').selectFile('cypress/fixtures/example.json')
|
||||
break
|
||||
case 'origin':
|
||||
cy.origin('http://barbaz.com:3500', () => {})
|
||||
break
|
||||
default:
|
||||
throw new Error(`Command not supported: ${command}`)
|
||||
}
|
||||
}
|
||||
const runString = run.toString()
|
||||
|
||||
// instead of running this script in the AUT, this appends it to the
|
||||
// spec frame to run it there
|
||||
if (appendToSpecFrame) {
|
||||
cy.wait(500) // gives the script time to run without the queue ending
|
||||
|
||||
const beforeScript = specFrame.document.createElement('script')
|
||||
|
||||
beforeScript.textContent = `
|
||||
window.__search = '${window.location.search.replace('appendToSpecFrame=true&', '')}'
|
||||
window.__isSpecFrame = true
|
||||
`
|
||||
|
||||
specFrame.document.body.appendChild(beforeScript)
|
||||
|
||||
const scriptEl = specFrame.document.createElement('script')
|
||||
|
||||
scriptEl.src = '/fixtures/aut-commands.js'
|
||||
specFrame.document.body.appendChild(scriptEl)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
switch (strategy) {
|
||||
case 'inline':
|
||||
run()
|
||||
break
|
||||
case 'then':
|
||||
cy.then(run)
|
||||
break
|
||||
case 'eval':
|
||||
specFrame.eval(`(command) => { (${runString})() }`)(command)
|
||||
break
|
||||
case 'function': {
|
||||
const fn = new specFrame.Function('command', `(${runString})()`)
|
||||
|
||||
fn.call(specFrame, command)
|
||||
break
|
||||
}
|
||||
default:
|
||||
throw new Error(`Strategy not supported: ${strategy}`)
|
||||
}
|
||||
})()
|
||||
@@ -1,4 +1,4 @@
|
||||
const fs = require('fs')
|
||||
const fs = require('fs-extra')
|
||||
const auth = require('basic-auth')
|
||||
const bodyParser = require('body-parser')
|
||||
const express = require('express')
|
||||
@@ -355,11 +355,24 @@ const createApp = (port) => {
|
||||
const el = document.createElement('p')
|
||||
el.id = 'p' + i
|
||||
el.innerHTML = 'x'.repeat(100000)
|
||||
|
||||
|
||||
document.body.appendChild(el)
|
||||
}
|
||||
</script>
|
||||
</html>
|
||||
</html>
|
||||
`)
|
||||
})
|
||||
|
||||
app.get('/aut-commands', async (req, res) => {
|
||||
const script = (await fs.readFileAsync(path.join(__dirname, '..', 'fixtures', 'aut-commands.js'))).toString()
|
||||
|
||||
res.send(`
|
||||
<html>
|
||||
<body>
|
||||
<input type="file" />
|
||||
<script>${script}</script>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
})
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ if (!isActuallyInteractive) {
|
||||
Cypress.config('retries', 2)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
let ranPrivilegedCommandsInBeforeEach = false
|
||||
|
||||
beforeEach(function () {
|
||||
// always set that we're interactive so we
|
||||
// get consistent passes and failures when running
|
||||
// from CI and when running in GUI mode
|
||||
@@ -30,6 +32,25 @@ beforeEach(() => {
|
||||
try {
|
||||
$(cy.state('window')).off()
|
||||
} catch (error) {} // eslint-disable-line no-empty
|
||||
|
||||
// only want to run this as part of the privileged commands spec
|
||||
if (cy.config('spec').baseName === 'privileged_commands.cy.ts') {
|
||||
cy.visit('/fixtures/files-form.html')
|
||||
|
||||
// it only needs to run once per spec run
|
||||
if (ranPrivilegedCommandsInBeforeEach) return
|
||||
|
||||
ranPrivilegedCommandsInBeforeEach = true
|
||||
|
||||
cy.exec('echo "hello"')
|
||||
cy.readFile('cypress/fixtures/app.json')
|
||||
cy.writeFile('cypress/_test-output/written.json', 'contents')
|
||||
cy.task('return:arg', 'arg')
|
||||
cy.get('#basic').selectFile('cypress/fixtures/valid.json')
|
||||
if (!Cypress.isBrowser({ family: 'webkit' })) {
|
||||
cy.origin('http://foobar.com:3500', () => {})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// this is here to test that cy.origin() dependencies used directly in the
|
||||
|
||||
@@ -172,6 +172,29 @@ export const makeRequestForCookieBehaviorTests = (
|
||||
})
|
||||
}
|
||||
|
||||
function runCommands () {
|
||||
cy.exec('echo "hello"')
|
||||
cy.readFile('cypress/fixtures/app.json')
|
||||
cy.writeFile('cypress/_test-output/written.json', 'contents')
|
||||
cy.task('return:arg', 'arg')
|
||||
cy.get('#basic').selectFile('cypress/fixtures/valid.json')
|
||||
if (!Cypress.isBrowser({ family: 'webkit' })) {
|
||||
cy.origin('http://foobar.com:3500', () => {})
|
||||
}
|
||||
}
|
||||
|
||||
export const runImportedPrivilegedCommands = runCommands
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
runGlobalPrivilegedCommands: () => void
|
||||
}
|
||||
}
|
||||
|
||||
window.runGlobalPrivilegedCommands = runCommands
|
||||
|
||||
Cypress.Commands.add('runSupportFileCustomPrivilegedCommands', runCommands)
|
||||
|
||||
Cypress.Commands.addQuery('getAll', getAllFn)
|
||||
|
||||
Cypress.Commands.add('shouldWithTimeout', shouldWithTimeout)
|
||||
|
||||
1
packages/driver/foo.json
Normal file
1
packages/driver/foo.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
1
packages/driver/foo.txt
Normal file
1
packages/driver/foo.txt
Normal file
@@ -0,0 +1 @@
|
||||
contents
|
||||
@@ -167,6 +167,16 @@ export class PrimaryOriginCommunicator extends EventEmitter {
|
||||
preprocessedData.args = data.args
|
||||
}
|
||||
|
||||
// if the data has an error/err, it needs special handling for Firefox or
|
||||
// else it will end up ignored because it's not structured-cloneable
|
||||
if (data?.error) {
|
||||
preprocessedData.error = preprocessForSerialization(data.error)
|
||||
}
|
||||
|
||||
if (data?.err) {
|
||||
preprocessedData.err = preprocessForSerialization(data.err)
|
||||
}
|
||||
|
||||
// If there is no crossOriginDriverWindows, there is no need to send the message.
|
||||
source.postMessage({
|
||||
event,
|
||||
|
||||
@@ -8,6 +8,10 @@ export const handleSocketEvents = (Cypress) => {
|
||||
timeout: Cypress.config().defaultCommandTimeout,
|
||||
})
|
||||
|
||||
if (response && response.error) {
|
||||
return callback({ error: response.error })
|
||||
}
|
||||
|
||||
callback({ response })
|
||||
}
|
||||
|
||||
|
||||
@@ -181,9 +181,16 @@ export const handleOriginFn = (Cypress: Cypress.Cypress, cy: $Cy) => {
|
||||
Cypress.specBridgeCommunicator.toPrimary('queue:finished', { err }, { syncGlobals: true })
|
||||
})
|
||||
|
||||
// the name of this function is used to verify if privileged commands are
|
||||
// properly called. it shouldn't be removed and if the name is changed, it
|
||||
// needs to also be changed in server/lib/browsers/privileged-channel.js
|
||||
function invokeOriginFn (callback) {
|
||||
return window.eval(`(${callback})`)(args)
|
||||
}
|
||||
|
||||
try {
|
||||
const callback = await getCallbackFn(fn, file)
|
||||
const value = window.eval(`(${callback})`)(args)
|
||||
const value = invokeOriginFn(callback)
|
||||
|
||||
// If we detect a non promise value with commands in queue, throw an error
|
||||
if (value && cy.queue.length > 0 && !value.then) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import $dom from '../../../dom'
|
||||
import $errUtils from '../../../cypress/error_utils'
|
||||
import $actionability from '../../actionability'
|
||||
import { addEventCoords, dispatch } from './trigger'
|
||||
import { runPrivilegedCommand, trimUserArgs } from '../../../util/privileged_channel'
|
||||
|
||||
/* dropzone.js relies on an experimental, nonstandard API, webkitGetAsEntry().
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry
|
||||
@@ -82,6 +83,15 @@ interface InternalSelectFileOptions extends Cypress.SelectFileOptions {
|
||||
eventTarget: JQuery
|
||||
}
|
||||
|
||||
interface FilePathObject {
|
||||
fileName?: string
|
||||
index: number
|
||||
isFilePath: boolean
|
||||
lastModified?: number
|
||||
mimeType?: string
|
||||
path: string
|
||||
}
|
||||
|
||||
const ACTIONS = {
|
||||
select: (element, dataTransfer, coords, state) => {
|
||||
(element as HTMLInputElement).files = dataTransfer.files
|
||||
@@ -153,32 +163,64 @@ export default (Commands, Cypress, cy, state, config) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Uses backend read:file rather than cy.readFile because we don't want to retry
|
||||
// loading a specific file until timeout, but rather retry the selectFile command as a whole
|
||||
const handlePath = async (file, options) => {
|
||||
return Cypress.backend('read:file', file.contents, { encoding: null })
|
||||
.then(({ contents }) => {
|
||||
return {
|
||||
// We default to the filename on the path, but allow them to override
|
||||
fileName: basename(file.contents),
|
||||
...file,
|
||||
contents: Cypress.Buffer.from(contents),
|
||||
}
|
||||
const readFiles = async (filePaths, options, userArgs) => {
|
||||
if (!filePaths.length) return []
|
||||
|
||||
// This reads the file with privileged access in the same manner as
|
||||
// cy.readFile(). We call directly into the backend rather than calling
|
||||
// cy.readFile() directly because we don't want to retry loading a specific
|
||||
// file until timeout, but rather retry the selectFile command as a whole
|
||||
return runPrivilegedCommand({
|
||||
commandName: 'selectFile',
|
||||
cy,
|
||||
Cypress: (Cypress as unknown) as InternalCypress.Cypress,
|
||||
options: {
|
||||
files: filePaths,
|
||||
},
|
||||
userArgs,
|
||||
})
|
||||
.then((results) => {
|
||||
return results.map((result) => {
|
||||
return {
|
||||
// We default to the filename on the path, but allow them to override
|
||||
fileName: basename(result.path),
|
||||
...result,
|
||||
contents: Cypress.Buffer.from(result.contents),
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.isNonSpec) {
|
||||
$errUtils.throwErrByPath('miscellaneous.non_spec_invocation', {
|
||||
args: { cmd: 'selectFile' },
|
||||
})
|
||||
}
|
||||
|
||||
if (err.code === 'ENOENT') {
|
||||
$errUtils.throwErrByPath('files.nonexistent', {
|
||||
args: { cmd: 'selectFile', file: file.contents, filePath: err.filePath },
|
||||
args: { cmd: 'selectFile', file: err.originalFilePath, filePath: err.filePath },
|
||||
})
|
||||
}
|
||||
|
||||
$errUtils.throwErrByPath('files.unexpected_error', {
|
||||
onFail: options._log,
|
||||
args: { cmd: 'selectFile', action: 'read', file, filePath: err.filePath, error: err.message },
|
||||
args: { cmd: 'selectFile', action: 'read', file: err.originalFilePath, filePath: err.filePath, error: err.message },
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const getFilePathObject = (file, index) => {
|
||||
return {
|
||||
encoding: null,
|
||||
fileName: file.fileName,
|
||||
index,
|
||||
isFilePath: true,
|
||||
lastModified: file.lastModified,
|
||||
mimeType: file.mimeType,
|
||||
path: file.contents,
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Turns a user-provided file - a string shorthand, ArrayBuffer, or object
|
||||
* into an object of form {
|
||||
@@ -191,7 +233,7 @@ export default (Commands, Cypress, cy, state, config) => {
|
||||
* we warn them and suggest how to fix it.
|
||||
*/
|
||||
const parseFile = (options) => {
|
||||
return async (file: any, index: number, filesArray: any[]): Promise<Cypress.FileReferenceObject> => {
|
||||
return (file: any, index: number, filesArray: any[]): Cypress.FileReferenceObject | FilePathObject => {
|
||||
if (typeof file === 'string' || ArrayBuffer.isView(file)) {
|
||||
file = { contents: file }
|
||||
}
|
||||
@@ -212,10 +254,13 @@ export default (Commands, Cypress, cy, state, config) => {
|
||||
}
|
||||
|
||||
if (typeof file.contents === 'string') {
|
||||
file = handleAlias(file, options) ?? await handlePath(file, options)
|
||||
// if not an alias, an object representing that the file is a path that
|
||||
// needs to be read from disk. contents are an empty string to they
|
||||
// it skips the next check
|
||||
file = handleAlias(file, options) ?? getFilePathObject(file, index)
|
||||
}
|
||||
|
||||
if (!_.isString(file.contents) && !ArrayBuffer.isView(file.contents)) {
|
||||
if (!file.isFilePath && !_.isString(file.contents) && !ArrayBuffer.isView(file.contents)) {
|
||||
file.contents = JSON.stringify(file.contents)
|
||||
}
|
||||
|
||||
@@ -223,8 +268,24 @@ export default (Commands, Cypress, cy, state, config) => {
|
||||
}
|
||||
}
|
||||
|
||||
async function collectFiles (files, options, userArgs) {
|
||||
const filesCollection = ([] as (Cypress.FileReference | FilePathObject)[]).concat(files).map(parseFile(options))
|
||||
// if there are any file paths, read them from the server in one go
|
||||
const filePaths = filesCollection.filter((file) => (file as FilePathObject).isFilePath)
|
||||
const filePathResults = await readFiles(filePaths, options, userArgs)
|
||||
|
||||
// stitch them back into the collection
|
||||
filePathResults.forEach((filePathResult) => {
|
||||
filesCollection[filePathResult.index] = _.pick(filePathResult, 'contents', 'fileName', 'mimeType', 'lastModified')
|
||||
})
|
||||
|
||||
return filesCollection as Cypress.FileReferenceObject[]
|
||||
}
|
||||
|
||||
Commands.addAll({ prevSubject: 'element' }, {
|
||||
async selectFile (subject: JQuery<any>, files: Cypress.FileReference | Cypress.FileReference[], options: Partial<InternalSelectFileOptions>): Promise<JQuery> {
|
||||
const userArgs = trimUserArgs([files, _.isObject(options) ? { ...options } : undefined])
|
||||
|
||||
options = _.defaults({}, options, {
|
||||
action: 'select',
|
||||
log: true,
|
||||
@@ -287,8 +348,7 @@ export default (Commands, Cypress, cy, state, config) => {
|
||||
}
|
||||
|
||||
// Make sure files is an array even if the user only passed in one
|
||||
const filesArray = await Promise.all(([] as Cypress.FileReference[]).concat(files).map(parseFile(options)))
|
||||
|
||||
const filesArray = await collectFiles(files, options, userArgs)
|
||||
const subjectChain = cy.subjectChain()
|
||||
|
||||
// We verify actionability on the subject, rather than the eventTarget,
|
||||
|
||||
@@ -3,18 +3,24 @@ import Promise from 'bluebird'
|
||||
|
||||
import $errUtils from '../../cypress/error_utils'
|
||||
import type { Log } from '../../cypress/log'
|
||||
import { runPrivilegedCommand, trimUserArgs } from '../../util/privileged_channel'
|
||||
|
||||
interface InternalExecOptions extends Partial<Cypress.ExecOptions> {
|
||||
_log?: Log
|
||||
cmd?: string
|
||||
timeout: number
|
||||
}
|
||||
|
||||
export default (Commands, Cypress, cy) => {
|
||||
Commands.addAll({
|
||||
exec (cmd: string, userOptions: Partial<Cypress.ExecOptions> = {}) {
|
||||
exec (cmd: string, userOptions: Partial<Cypress.ExecOptions>) {
|
||||
const userArgs = trimUserArgs([cmd, userOptions])
|
||||
|
||||
userOptions = userOptions || {}
|
||||
|
||||
const options: InternalExecOptions = _.defaults({}, userOptions, {
|
||||
log: true,
|
||||
timeout: Cypress.config('execTimeout'),
|
||||
timeout: Cypress.config('execTimeout') as number,
|
||||
failOnNonZeroExit: true,
|
||||
env: {},
|
||||
})
|
||||
@@ -46,7 +52,13 @@ export default (Commands, Cypress, cy) => {
|
||||
// because we're handling timeouts ourselves
|
||||
cy.clearTimeout()
|
||||
|
||||
return Cypress.backend('exec', _.pick(options, 'cmd', 'timeout', 'env'))
|
||||
return runPrivilegedCommand({
|
||||
commandName: 'exec',
|
||||
cy,
|
||||
Cypress: (Cypress as unknown) as InternalCypress.Cypress,
|
||||
options: _.pick(options, 'cmd', 'timeout', 'env'),
|
||||
userArgs,
|
||||
})
|
||||
.timeout(options.timeout)
|
||||
.then((result) => {
|
||||
if (options._log) {
|
||||
@@ -75,20 +87,26 @@ export default (Commands, Cypress, cy) => {
|
||||
})
|
||||
})
|
||||
.catch(Promise.TimeoutError, { timedOut: true }, () => {
|
||||
return $errUtils.throwErrByPath('exec.timed_out', {
|
||||
$errUtils.throwErrByPath('exec.timed_out', {
|
||||
onFail: options._log,
|
||||
args: { cmd, timeout: options.timeout },
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch((err) => {
|
||||
// re-throw if timedOut error from above
|
||||
if (error.name === 'CypressError') {
|
||||
throw error
|
||||
if (err.name === 'CypressError') {
|
||||
throw err
|
||||
}
|
||||
|
||||
return $errUtils.throwErrByPath('exec.failed', {
|
||||
if (err.isNonSpec) {
|
||||
$errUtils.throwErrByPath('miscellaneous.non_spec_invocation', {
|
||||
args: { cmd: 'exec' },
|
||||
})
|
||||
}
|
||||
|
||||
$errUtils.throwErrByPath('exec.failed', {
|
||||
onFail: options._log,
|
||||
args: { cmd, error },
|
||||
args: { cmd, error: err },
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
@@ -3,30 +3,37 @@ import { basename } from 'path'
|
||||
|
||||
import $errUtils from '../../cypress/error_utils'
|
||||
import type { Log } from '../../cypress/log'
|
||||
import { runPrivilegedCommand, trimUserArgs } from '../../util/privileged_channel'
|
||||
|
||||
interface InternalWriteFileOptions extends Partial<Cypress.WriteFileOptions & Cypress.Timeoutable> {
|
||||
_log?: Log
|
||||
timeout: number
|
||||
}
|
||||
|
||||
interface ReadFileOptions extends Partial<Cypress.Loggable & Cypress.Timeoutable> {
|
||||
encoding?: Cypress.Encodings
|
||||
}
|
||||
|
||||
interface InternalWriteFileOptions extends Partial<Cypress.WriteFileOptions & Cypress.Timeoutable> {
|
||||
_log?: Log
|
||||
}
|
||||
type WriteFileOptions = Partial<Cypress.WriteFileOptions & Cypress.Timeoutable>
|
||||
|
||||
export default (Commands, Cypress, cy, state) => {
|
||||
Commands.addQuery('readFile', function readFile (file, encoding, options: ReadFileOptions = {}) {
|
||||
Commands.addQuery('readFile', function readFile (file: string, encoding: Cypress.Encodings | ReadFileOptions | undefined, userOptions?: ReadFileOptions) {
|
||||
const userArgs = trimUserArgs([file, encoding, _.isObject(userOptions) ? { ...userOptions } : undefined])
|
||||
|
||||
if (_.isObject(encoding)) {
|
||||
options = encoding
|
||||
encoding = options.encoding
|
||||
userOptions = encoding
|
||||
encoding = userOptions.encoding
|
||||
}
|
||||
|
||||
userOptions = userOptions || {}
|
||||
|
||||
encoding = encoding === undefined ? 'utf8' : encoding
|
||||
|
||||
const timeout = options.timeout ?? Cypress.config('defaultCommandTimeout')
|
||||
const timeout = userOptions.timeout ?? Cypress.config('defaultCommandTimeout') as number
|
||||
|
||||
this.set('timeout', timeout)
|
||||
this.set('ensureExistenceFor', 'subject')
|
||||
|
||||
const log = options.log !== false && Cypress.log({ message: file, timeout })
|
||||
const log = userOptions.log !== false && Cypress.log({ message: file, timeout })
|
||||
|
||||
if (!file || !_.isString(file)) {
|
||||
$errUtils.throwErrByPath('files.invalid_argument', {
|
||||
@@ -48,7 +55,16 @@ export default (Commands, Cypress, cy, state) => {
|
||||
}
|
||||
|
||||
fileResult = null
|
||||
filePromise = Cypress.backend('read:file', file, { encoding })
|
||||
filePromise = runPrivilegedCommand({
|
||||
commandName: 'readFile',
|
||||
cy,
|
||||
Cypress: (Cypress as unknown) as InternalCypress.Cypress,
|
||||
options: {
|
||||
file,
|
||||
encoding,
|
||||
},
|
||||
userArgs,
|
||||
})
|
||||
.timeout(timeout)
|
||||
.then((result) => {
|
||||
// https://github.com/cypress-io/cypress/issues/1558
|
||||
@@ -72,6 +88,12 @@ export default (Commands, Cypress, cy, state) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (err.isNonSpec) {
|
||||
$errUtils.throwErrByPath('miscellaneous.non_spec_invocation', {
|
||||
args: { cmd: 'readFile' },
|
||||
})
|
||||
}
|
||||
|
||||
// Non-ENOENT errors are not retried
|
||||
if (err.code !== 'ENOENT') {
|
||||
$errUtils.throwErrByPath('files.unexpected_error', {
|
||||
@@ -130,12 +152,16 @@ export default (Commands, Cypress, cy, state) => {
|
||||
})
|
||||
|
||||
Commands.addAll({
|
||||
writeFile (fileName, contents, encoding, userOptions: Partial<Cypress.WriteFileOptions & Cypress.Timeoutable> = {}) {
|
||||
writeFile (fileName: string, contents: string, encoding: Cypress.Encodings | WriteFileOptions | undefined, userOptions: WriteFileOptions) {
|
||||
const userArgs = trimUserArgs([fileName, contents, encoding, _.isObject(userOptions) ? { ...userOptions } : undefined])
|
||||
|
||||
if (_.isObject(encoding)) {
|
||||
userOptions = encoding
|
||||
encoding = undefined
|
||||
}
|
||||
|
||||
userOptions = userOptions || {}
|
||||
|
||||
const options: InternalWriteFileOptions = _.defaults({}, userOptions, {
|
||||
// https://github.com/cypress-io/cypress/issues/1558
|
||||
// If no encoding is specified, then Cypress has historically defaulted
|
||||
@@ -182,7 +208,19 @@ export default (Commands, Cypress, cy, state) => {
|
||||
// the timeout ourselves
|
||||
cy.clearTimeout()
|
||||
|
||||
return Cypress.backend('write:file', fileName, contents, _.pick(options, 'encoding', 'flag')).timeout(options.timeout)
|
||||
return runPrivilegedCommand({
|
||||
commandName: 'writeFile',
|
||||
cy,
|
||||
Cypress: (Cypress as unknown) as InternalCypress.Cypress,
|
||||
options: {
|
||||
fileName,
|
||||
contents,
|
||||
encoding: options.encoding,
|
||||
flag: options.flag,
|
||||
},
|
||||
userArgs,
|
||||
})
|
||||
.timeout(options.timeout)
|
||||
.then(({ filePath, contents }) => {
|
||||
consoleProps['File Path'] = filePath
|
||||
consoleProps['Contents'] = contents
|
||||
@@ -197,6 +235,12 @@ export default (Commands, Cypress, cy, state) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (err.isNonSpec) {
|
||||
return $errUtils.throwErrByPath('miscellaneous.non_spec_invocation', {
|
||||
args: { cmd: 'writeFile' },
|
||||
})
|
||||
}
|
||||
|
||||
return $errUtils.throwErrByPath('files.unexpected_error', {
|
||||
onFail: options._log,
|
||||
args: { cmd: 'writeFile', action: 'write', file: fileName, filePath: err.filePath, error: err.message },
|
||||
|
||||
@@ -9,6 +9,7 @@ import { $Location } from '../../../cypress/location'
|
||||
import { LogUtils } from '../../../cypress/log'
|
||||
import logGroup from '../../logGroup'
|
||||
import type { StateFunc } from '../../../cypress/state'
|
||||
import { runPrivilegedCommand, trimUserArgs } from '../../../util/privileged_channel'
|
||||
|
||||
const reHttp = /^https?:\/\//
|
||||
|
||||
@@ -23,15 +24,32 @@ const normalizeOrigin = (urlOrDomain) => {
|
||||
return $Location.normalize(origin)
|
||||
}
|
||||
|
||||
type OptionsOrFn<T> = { args: T } | (() => {})
|
||||
type Fn<T> = (args?: T) => {}
|
||||
|
||||
function stringifyFn (fn?: any) {
|
||||
return _.isFunction(fn) ? fn.toString() : undefined
|
||||
}
|
||||
|
||||
function getUserArgs<T> (urlOrDomain: string, optionsOrFn: OptionsOrFn<T>, fn?: Fn<T>) {
|
||||
return trimUserArgs([
|
||||
urlOrDomain,
|
||||
fn && _.isObject(optionsOrFn) ? { ...optionsOrFn } : stringifyFn(optionsOrFn),
|
||||
fn ? stringifyFn(fn) : undefined,
|
||||
])
|
||||
}
|
||||
|
||||
export default (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: StateFunc, config: Cypress.InternalConfig) => {
|
||||
const communicator = Cypress.primaryOriginCommunicator
|
||||
|
||||
Commands.addAll({
|
||||
origin<T> (urlOrDomain: string, optionsOrFn: { args: T } | (() => {}), fn?: (args?: T) => {}) {
|
||||
origin<T> (urlOrDomain: string, optionsOrFn: OptionsOrFn<T>, fn?: Fn<T>) {
|
||||
if (Cypress.isBrowser('webkit')) {
|
||||
return $errUtils.throwErrByPath('webkit.origin')
|
||||
}
|
||||
|
||||
const userArgs = getUserArgs<T>(urlOrDomain, optionsOrFn, fn)
|
||||
|
||||
const userInvocationStack = state('current').get('userInvocationStack')
|
||||
|
||||
// store the invocation stack in the case that `cy.origin` errors
|
||||
@@ -185,9 +203,21 @@ export default (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: State
|
||||
const fn = _.isFunction(callbackFn) ? callbackFn.toString() : callbackFn
|
||||
const file = $stackUtils.getSourceDetailsForFirstLine(userInvocationStack, config('projectRoot'))?.absoluteFile
|
||||
|
||||
// once the secondary origin page loads, send along the
|
||||
// user-specified callback to run in that origin
|
||||
try {
|
||||
// origin is a privileged command, meaning it has to be invoked
|
||||
// from the spec or support file
|
||||
await runPrivilegedCommand({
|
||||
commandName: 'origin',
|
||||
cy,
|
||||
Cypress: (Cypress as unknown) as InternalCypress.Cypress,
|
||||
options: {
|
||||
specBridgeOrigin,
|
||||
},
|
||||
userArgs,
|
||||
})
|
||||
|
||||
// once the secondary origin page loads, send along the
|
||||
// user-specified callback to run in that origin
|
||||
communicator.toSpecBridge(origin, 'run:origin:fn', {
|
||||
args: options?.args || undefined,
|
||||
fn,
|
||||
@@ -212,6 +242,12 @@ export default (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: State
|
||||
logCounter: LogUtils.getCounter(),
|
||||
})
|
||||
} catch (err: any) {
|
||||
if (err.isNonSpec) {
|
||||
return _reject($errUtils.errByPath('miscellaneous.non_spec_invocation', {
|
||||
cmd: 'origin',
|
||||
}))
|
||||
}
|
||||
|
||||
const wrappedErr = $errUtils.errByPath('origin.run_origin_fn_errored', {
|
||||
error: err.message,
|
||||
})
|
||||
|
||||
@@ -5,17 +5,23 @@ import $utils from '../../cypress/utils'
|
||||
import $errUtils from '../../cypress/error_utils'
|
||||
import $stackUtils from '../../cypress/stack_utils'
|
||||
import type { Log } from '../../cypress/log'
|
||||
import { runPrivilegedCommand, trimUserArgs } from '../../util/privileged_channel'
|
||||
|
||||
interface InternalTaskOptions extends Partial<Cypress.Loggable & Cypress.Timeoutable> {
|
||||
_log?: Log
|
||||
timeout: number
|
||||
}
|
||||
|
||||
export default (Commands, Cypress, cy) => {
|
||||
Commands.addAll({
|
||||
task (task, arg, userOptions: Partial<Cypress.Loggable & Cypress.Timeoutable> = {}) {
|
||||
task (task, arg, userOptions: Partial<Cypress.Loggable & Cypress.Timeoutable>) {
|
||||
const userArgs = trimUserArgs([task, arg, _.isObject(userOptions) ? { ...userOptions } : undefined])
|
||||
|
||||
userOptions = userOptions || {}
|
||||
|
||||
const options: InternalTaskOptions = _.defaults({}, userOptions, {
|
||||
log: true,
|
||||
timeout: Cypress.config('taskTimeout'),
|
||||
timeout: Cypress.config('taskTimeout') as number,
|
||||
})
|
||||
|
||||
let consoleOutput
|
||||
@@ -52,10 +58,16 @@ export default (Commands, Cypress, cy) => {
|
||||
// because we're handling timeouts ourselves
|
||||
cy.clearTimeout()
|
||||
|
||||
return Cypress.backend('task', {
|
||||
task,
|
||||
arg,
|
||||
timeout: options.timeout,
|
||||
return runPrivilegedCommand({
|
||||
commandName: 'task',
|
||||
cy,
|
||||
Cypress: (Cypress as unknown) as InternalCypress.Cypress,
|
||||
userArgs,
|
||||
options: {
|
||||
task,
|
||||
arg,
|
||||
timeout: options.timeout,
|
||||
},
|
||||
})
|
||||
.timeout(options.timeout)
|
||||
.then((result) => {
|
||||
@@ -71,7 +83,7 @@ export default (Commands, Cypress, cy) => {
|
||||
args: { task, timeout: options.timeout },
|
||||
})
|
||||
})
|
||||
.catch({ timedOut: true }, (error) => {
|
||||
.catch({ timedOut: true }, (error: any) => {
|
||||
$errUtils.throwErrByPath('task.server_timed_out', {
|
||||
onFail: options._log,
|
||||
args: { task, timeout: options.timeout, error: error.message },
|
||||
@@ -83,6 +95,12 @@ export default (Commands, Cypress, cy) => {
|
||||
throw err
|
||||
}
|
||||
|
||||
if (err.isNonSpec) {
|
||||
$errUtils.throwErrByPath('miscellaneous.non_spec_invocation', {
|
||||
args: { cmd: 'task' },
|
||||
})
|
||||
}
|
||||
|
||||
err.stack = $stackUtils.normalizedStack(err)
|
||||
|
||||
if (err?.isKnownError) {
|
||||
|
||||
@@ -45,6 +45,7 @@ import { setupAutEventHandlers } from './cypress/aut_event_handlers'
|
||||
|
||||
import type { CachedTestState } from '@packages/types'
|
||||
import * as cors from '@packages/network/lib/cors'
|
||||
import { setSpecContentSecurityPolicy } from './util/privileged_channel'
|
||||
|
||||
import { telemetry } from '@packages/telemetry/src/browser'
|
||||
|
||||
@@ -56,6 +57,8 @@ declare global {
|
||||
Cypress: Cypress.Cypress
|
||||
Runner: any
|
||||
cy: Cypress.cy
|
||||
// eval doesn't exist on the built-in Window type for some reason
|
||||
eval (expression: string): any
|
||||
}
|
||||
}
|
||||
|
||||
@@ -344,7 +347,17 @@ class $Cypress {
|
||||
|
||||
this.events.proxyTo(this.cy)
|
||||
|
||||
$scriptUtils.runScripts(specWindow, scripts)
|
||||
$scriptUtils.runScripts({
|
||||
browser: this.config('browser'),
|
||||
scripts,
|
||||
specWindow,
|
||||
testingType: this.testingType,
|
||||
})
|
||||
.then(() => {
|
||||
if (this.testingType === 'e2e') {
|
||||
return setSpecContentSecurityPolicy(specWindow)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
this.runner.onSpecError('error')({ error })
|
||||
})
|
||||
|
||||
@@ -20,13 +20,15 @@ export class $Chainer {
|
||||
|
||||
static add (key, fn) {
|
||||
$Chainer.prototype[key] = function (...args) {
|
||||
const verificationPromise = Cypress.emitMap('command:invocation', { name: key, args })
|
||||
|
||||
const userInvocationStack = $stackUtils.normalizedUserInvocationStack(
|
||||
(new this.specWindow.Error('command invocation stack')).stack,
|
||||
)
|
||||
|
||||
// call back the original function with our new args
|
||||
// pass args an as array and not a destructured invocation
|
||||
fn(this, userInvocationStack, args)
|
||||
fn(this, userInvocationStack, args, verificationPromise)
|
||||
|
||||
// return the chainer so additional calls
|
||||
// are slurped up by the chainer instead of cy
|
||||
|
||||
@@ -683,7 +683,7 @@ export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssert
|
||||
const cyFn = wrap(true)
|
||||
const chainerFn = wrap(false)
|
||||
|
||||
const callback = (chainer, userInvocationStack, args, firstCall = false) => {
|
||||
const callback = (chainer, userInvocationStack, args, verificationPromise, firstCall = false) => {
|
||||
// dont enqueue / inject any new commands if
|
||||
// onInjectCommand returns false
|
||||
const onInjectCommand = cy.state('onInjectCommand')
|
||||
@@ -699,6 +699,7 @@ export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssert
|
||||
chainerId: chainer.chainerId,
|
||||
userInvocationStack,
|
||||
fn: firstCall ? cyFn : chainerFn,
|
||||
verificationPromise,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -707,6 +708,15 @@ export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssert
|
||||
cy[name] = function (...args) {
|
||||
ensureRunnable(cy, name)
|
||||
|
||||
// for privileged commands, we send a message to the server that verifies
|
||||
// them as coming from the spec. the fulfillment of this promise means
|
||||
// the message was received. the implementation for those commands
|
||||
// checks to make sure this promise is fulfilled before sending its
|
||||
// websocket message for running the command to ensure prevent a race
|
||||
// condition where running the command happens before the command is
|
||||
// verified
|
||||
const verificationPromise = Cypress.emitMap('command:invocation', { name, args })
|
||||
|
||||
// this is the first call on cypress
|
||||
// so create a new chainer instance
|
||||
const chainer = new $Chainer(cy.specWindow)
|
||||
@@ -717,7 +727,7 @@ export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssert
|
||||
|
||||
const userInvocationStack = $stackUtils.captureUserInvocationStack(cy.specWindow.Error)
|
||||
|
||||
callback(chainer, userInvocationStack, args, true)
|
||||
callback(chainer, userInvocationStack, args, verificationPromise, true)
|
||||
|
||||
// if we are in the middle of a command
|
||||
// and its return value is a promise
|
||||
@@ -761,7 +771,7 @@ export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssert
|
||||
|
||||
this.queryFns[name] = fn
|
||||
|
||||
const callback = (chainer, userInvocationStack, args) => {
|
||||
const callback = (chainer, userInvocationStack, args, verificationPromise) => {
|
||||
// dont enqueue / inject any new commands if
|
||||
// onInjectCommand returns false
|
||||
const onInjectCommand = cy.state('onInjectCommand')
|
||||
@@ -791,6 +801,7 @@ export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssert
|
||||
|
||||
cyFn.originalFn = fn
|
||||
command.set('fn', cyFn)
|
||||
command.set('verificationPromise', verificationPromise)
|
||||
|
||||
cy.enqueue(command)
|
||||
}
|
||||
@@ -800,6 +811,15 @@ export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssert
|
||||
cy[name] = function (...args) {
|
||||
ensureRunnable(cy, name)
|
||||
|
||||
// for privileged commands, we send a message to the server that verifies
|
||||
// them as coming from the spec. the fulfillment of this promise means
|
||||
// the message was received. the implementation for those commands
|
||||
// checks to make sure this promise is fulfilled before sending its
|
||||
// websocket message for running the command to ensure prevent a race
|
||||
// condition where running the command happens before the command is
|
||||
// verified
|
||||
const verificationPromise = Cypress.emitMap('command:invocation', { name, args })
|
||||
|
||||
// this is the first call on cypress
|
||||
// so create a new chainer instance
|
||||
const chainer = new $Chainer(cy.specWindow)
|
||||
@@ -810,7 +830,7 @@ export class $Cy extends EventEmitter2 implements ITimeouts, IStability, IAssert
|
||||
|
||||
const userInvocationStack = $stackUtils.captureUserInvocationStack(cy.specWindow.Error)
|
||||
|
||||
callback(chainer, userInvocationStack, args)
|
||||
callback(chainer, userInvocationStack, args, verificationPromise)
|
||||
|
||||
// if we're the first call onto a cy
|
||||
// command, then kick off the run
|
||||
|
||||
@@ -753,6 +753,7 @@ export default {
|
||||
},
|
||||
|
||||
miscellaneous: {
|
||||
non_spec_invocation: `${cmd('{{cmd}}')} must only be invoked from the spec file or support file.`,
|
||||
returned_value_and_commands_from_custom_command (obj) {
|
||||
return {
|
||||
message: stripIndent`\
|
||||
|
||||
@@ -42,9 +42,38 @@ const runScriptsFromUrls = (specWindow, scripts) => {
|
||||
.then((scripts) => evalScripts(specWindow, scripts))
|
||||
}
|
||||
|
||||
const appendScripts = (specWindow, scripts) => {
|
||||
return Bluebird.each(scripts, (script: any) => {
|
||||
const firstScript = specWindow.document.querySelector('script')
|
||||
const specScript = specWindow.document.createElement('script')
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
specScript.addEventListener('load', () => {
|
||||
resolve()
|
||||
})
|
||||
|
||||
specScript.src = script.relativeUrl
|
||||
firstScript.after(specScript)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
interface Script {
|
||||
absolute: string
|
||||
relative: string
|
||||
relativeUrl: string
|
||||
}
|
||||
|
||||
interface RunScriptsOptions {
|
||||
browser: Cypress.Browser
|
||||
scripts: Script[]
|
||||
specWindow: Window
|
||||
testingType: Cypress.TestingType
|
||||
}
|
||||
|
||||
// Supports either scripts as objects or as async import functions
|
||||
export default {
|
||||
runScripts: (specWindow, scripts) => {
|
||||
runScripts: ({ browser, scripts, specWindow, testingType }: RunScriptsOptions) => {
|
||||
// if scripts contains at least one promise
|
||||
if (scripts.length && typeof scripts[0] === 'function') {
|
||||
// chain the loading promises
|
||||
@@ -54,6 +83,15 @@ export default {
|
||||
return Bluebird.each(scripts, (script: any) => script())
|
||||
}
|
||||
|
||||
// in webkit, stack traces for e2e are made pretty much useless if these
|
||||
// scripts are eval'd, so we append them as script tags instead
|
||||
if (browser.family === 'webkit' && testingType === 'e2e') {
|
||||
return appendScripts(specWindow, scripts)
|
||||
}
|
||||
|
||||
// for other browsers, we get the contents of the scripts so that we can
|
||||
// extract and utilize the source maps for better errors and code frames.
|
||||
// we then eval the script contents to run them
|
||||
return runScriptsFromUrls(specWindow, scripts)
|
||||
},
|
||||
}
|
||||
|
||||
40
packages/driver/src/util/privileged_channel.ts
Normal file
40
packages/driver/src/util/privileged_channel.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import _ from 'lodash'
|
||||
import Bluebird from 'bluebird'
|
||||
|
||||
/**
|
||||
* prevents further scripts outside of our own and the spec itself from being
|
||||
* run in the spec frame
|
||||
* @param specWindow: Window
|
||||
*/
|
||||
export function setSpecContentSecurityPolicy (specWindow) {
|
||||
const metaEl = specWindow.document.createElement('meta')
|
||||
|
||||
metaEl.setAttribute('http-equiv', 'Content-Security-Policy')
|
||||
metaEl.setAttribute('content', `script-src 'unsafe-eval'`)
|
||||
specWindow.document.querySelector('head')!.appendChild(metaEl)
|
||||
}
|
||||
|
||||
interface RunPrivilegedCommandOptions {
|
||||
commandName: string
|
||||
cy: Cypress.cy
|
||||
Cypress: InternalCypress.Cypress
|
||||
options: any
|
||||
userArgs: any[]
|
||||
}
|
||||
|
||||
export function runPrivilegedCommand ({ commandName, cy, Cypress, options, userArgs }: RunPrivilegedCommandOptions): Bluebird<any> {
|
||||
return Bluebird.try(() => {
|
||||
return cy.state('current').get('verificationPromise')[0]
|
||||
})
|
||||
.then(() => {
|
||||
return Cypress.backend('run:privileged', {
|
||||
commandName,
|
||||
options,
|
||||
userArgs,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function trimUserArgs (args: any[]) {
|
||||
return _.dropRightWhile(args, _.isUndefined)
|
||||
}
|
||||
4
packages/driver/types/internal-types.d.ts
vendored
4
packages/driver/types/internal-types.d.ts
vendored
@@ -68,7 +68,9 @@ declare namespace Cypress {
|
||||
}
|
||||
|
||||
declare namespace InternalCypress {
|
||||
interface Cypress extends Cypress.Cypress, NodeEventEmitter {}
|
||||
interface Cypress extends Cypress.Cypress, NodeEventEmitter {
|
||||
backend: (eventName: string, ...args: any[]) => Promise<any>
|
||||
}
|
||||
|
||||
interface LocalStorage extends Cypress.LocalStorage {
|
||||
setStorages: (local, remote) => LocalStorage
|
||||
|
||||
2
packages/driver/types/spec-types.d.ts
vendored
2
packages/driver/types/spec-types.d.ts
vendored
@@ -5,5 +5,7 @@ declare namespace Cypress {
|
||||
originLoadUtils(origin: string): Chainable
|
||||
getAll(...aliases: string[]): Chainable
|
||||
shouldWithTimeout(cb: (subj: {}) => void, timeout?: number): Chainable
|
||||
runSpecFileCustomPrivilegedCommands(): Chainable
|
||||
runSupportFileCustomPrivilegedCommands(): Chainable
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,11 @@
|
||||
"from": "default",
|
||||
"field": "execTimeout"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
"field": "experimentalCspAllowList"
|
||||
},
|
||||
{
|
||||
"value": false,
|
||||
"from": "default",
|
||||
|
||||
@@ -544,6 +544,10 @@
|
||||
"experiments": {
|
||||
"title": "Experiments",
|
||||
"description": "If you'd like to try out new features that we're working on, you can enable beta features for your project by turning on the experimental features you'd like to try. {0}",
|
||||
"experimentalCspAllowList": {
|
||||
"name": "CSP Allow List",
|
||||
"description": "Enables Cypress to selectively permit Content-Security-Policy and Content-Security-Policy-Report-Only header directives, including those that might otherwise block Cypress from running."
|
||||
},
|
||||
"experimentalFetchPolyfill": {
|
||||
"name": "Fetch polyfill",
|
||||
"description": "Automatically replaces `window.fetch` with a polyfill that Cypress can spy on and stub. Note: `experimentalFetchPolyfill` has been deprecated in Cypress 6.0.0 and will be removed in a future release. Consider using [`cy.intercept()`](https://on.cypress.io/intercept) to intercept `fetch` requests instead."
|
||||
|
||||
@@ -52,7 +52,7 @@ enum BrowserStatus {
|
||||
}
|
||||
|
||||
"""
|
||||
When we don't have an immediate response for the cloudViewer request, we'll use this as a fallback to
|
||||
When we don't have an immediate response for the cloudViewer request, we'll use this as a fallback to
|
||||
render the avatar in the header bar / signal authenticated state immediately
|
||||
"""
|
||||
type CachedUser implements Node {
|
||||
@@ -1702,9 +1702,6 @@ type Mutation {
|
||||
"""Ping configured Base URL"""
|
||||
pingBaseUrl: Query
|
||||
|
||||
"""Removes the cache entries for specified cloudSpecByPath query records"""
|
||||
purgeCloudSpecByPathCache(projectSlug: String!, specPaths: [String!]!): Boolean
|
||||
|
||||
"""show the launchpad windows"""
|
||||
reconfigureProject: Boolean!
|
||||
|
||||
@@ -2055,12 +2052,18 @@ type RelevantRun {
|
||||
"""Information about the current commit for the local project"""
|
||||
currentCommitInfo: CommitInfo
|
||||
|
||||
"""Latest relevant runs to fetch for the specs and runs page"""
|
||||
latest: [RelevantRunInfo!]!
|
||||
|
||||
"""Run number of the selected run in use on the Debug page"""
|
||||
selectedRunNumber: Int
|
||||
}
|
||||
|
||||
"""runNumber and commitSha for a given run"""
|
||||
type RelevantRunInfo {
|
||||
"""The run id"""
|
||||
runId: ID!
|
||||
|
||||
"""The runNumber that these spec counts belong to"""
|
||||
runNumber: Int!
|
||||
|
||||
@@ -2073,7 +2076,9 @@ type RelevantRunInfo {
|
||||
|
||||
enum RelevantRunLocationEnum {
|
||||
DEBUG
|
||||
RUNS
|
||||
SIDEBAR
|
||||
SPECS
|
||||
}
|
||||
|
||||
"""
|
||||
@@ -2345,11 +2350,6 @@ type Subscription {
|
||||
|
||||
"""Issued when the watched specs for the project changes"""
|
||||
specsChange: CurrentProject
|
||||
|
||||
"""
|
||||
Initiates the polling mechanism with the Cypress Cloud to check if we should refetch specs, and mark specs as stale if we have updates
|
||||
"""
|
||||
startPollingForSpecs(branchName: String, projectId: String): String
|
||||
}
|
||||
|
||||
enum SupportStatusEnum {
|
||||
|
||||
@@ -22,12 +22,15 @@ export const nexusSlowGuardPlugin = plugin({
|
||||
|
||||
if (isPromiseLike(result) && threshold !== false) {
|
||||
const resolvePath = pathToArray(info.path)
|
||||
const start = process.hrtime.bigint()
|
||||
const hanging = setTimeout(() => {
|
||||
const operationId = `${info.operation.operation} ${info.operation.name?.value ?? `(anonymous)`}`
|
||||
|
||||
if (process.env.CYPRESS_INTERNAL_ENV !== 'production') {
|
||||
const totalMS = (process.hrtime.bigint() - start) / BigInt(1000000)
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(chalk.red(`\n\nNexusSlowGuard: Taking more than ${threshold}ms to execute ${JSON.stringify(resolvePath)} for ${operationId}\n\n`))
|
||||
console.error(chalk.red(`\n\nNexusSlowGuard: Taking more than ${threshold}ms to execute ${JSON.stringify(resolvePath)} for ${operationId} (total time ${totalMS}ms)\n\n`))
|
||||
}
|
||||
}, threshold)
|
||||
|
||||
|
||||
@@ -8,13 +8,10 @@ import { GenerateSpecResponse } from './gql-GenerateSpecResponse'
|
||||
import { Cohort, CohortInput } from './gql-Cohorts'
|
||||
import { Query } from './gql-Query'
|
||||
import { ScaffoldedFile } from './gql-ScaffoldedFile'
|
||||
import debugLib from 'debug'
|
||||
import { ReactComponentResponse } from './gql-ReactComponentResponse'
|
||||
import { TestsBySpecInput } from '../inputTypes'
|
||||
import { RunSpecResult } from '../unions'
|
||||
|
||||
const debug = debugLib('cypress:graphql:mutation')
|
||||
|
||||
export const mutation = mutationType({
|
||||
definition (t) {
|
||||
t.field('copyTextToClipboard', {
|
||||
@@ -681,25 +678,6 @@ export const mutation = mutationType({
|
||||
},
|
||||
})
|
||||
|
||||
t.field('purgeCloudSpecByPathCache', {
|
||||
type: 'Boolean',
|
||||
args: {
|
||||
projectSlug: nonNull(stringArg({})),
|
||||
specPaths: nonNull(list(nonNull(stringArg({})))),
|
||||
},
|
||||
description: 'Removes the cache entries for specified cloudSpecByPath query records',
|
||||
resolve: async (source, args, ctx) => {
|
||||
const { projectSlug, specPaths } = args
|
||||
|
||||
debug('Purging %d `cloudSpecByPath` cache records for project %s: %o', specPaths.length, projectSlug, specPaths)
|
||||
for (let specPath of specPaths) {
|
||||
await ctx.cloud.invalidate('Query', 'cloudSpecByPath', { projectSlug, specPath })
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
})
|
||||
|
||||
t.field('refetchRemote', {
|
||||
type: Query,
|
||||
description: 'Signal that we are explicitly refetching remote data and should not use the server cache',
|
||||
|
||||
@@ -4,6 +4,10 @@ export const RelevantRunInfo = objectType({
|
||||
name: 'RelevantRunInfo',
|
||||
description: 'runNumber and commitSha for a given run',
|
||||
definition (t) {
|
||||
t.nonNull.id('runId', {
|
||||
description: 'The run id',
|
||||
})
|
||||
|
||||
t.nonNull.int('runNumber', {
|
||||
description: 'The runNumber that these spec counts belong to',
|
||||
})
|
||||
@@ -28,6 +32,11 @@ export const RelevantRun = objectType({
|
||||
description: 'All relevant runs to fetch for the debug page prior to the latest completed run',
|
||||
})
|
||||
|
||||
t.nonNull.list.nonNull.field('latest', {
|
||||
type: RelevantRunInfo,
|
||||
description: 'Latest relevant runs to fetch for the specs and runs page',
|
||||
})
|
||||
|
||||
t.nonNull.int('commitsAhead', {
|
||||
description: 'How many commits ahead the current local commit is from the commit of the current run',
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user