Merge branch 'develop' into merge-develop-into-13

This commit is contained in:
Emily Rohrbough
2023-05-01 14:21:12 -05:00
633 changed files with 31788 additions and 5352 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
# Bump this version to force CI to re-create the cache from scratch.
12-12-22
04-19-22
+10 -10
View File
@@ -30,7 +30,7 @@ mainBuildFilters: &mainBuildFilters
- /^release\/\d+\.\d+\.\d+$/
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- 'update-v8-snapshot-cache-on-develop'
- 'emily/before-spec-promise'
- 'ryanm/feat/unify-cdp-approach-in-electron'
# usually we don't build Mac app - it takes a long time
# but sometimes we want to really confirm we are doing the right thing
@@ -41,7 +41,7 @@ macWorkflowFilters: &darwin-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'emily/before-spec-promise', << pipeline.git.branch >> ]
- equal: [ 'ryanm/feat/unify-cdp-approach-in-electron', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -52,7 +52,7 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'fix/preflight', << pipeline.git.branch >> ]
- equal: [ 'ryanm/feat/unify-cdp-approach-in-electron', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
@@ -71,12 +71,12 @@ windowsWorkflowFilters: &windows-workflow-filters
or:
- equal: [ develop, << pipeline.git.branch >> ]
# use the following branch as well to ensure that v8 snapshot cache updates are fully tested
- equal: [ 'lmiller/fixing-vite-windows', << pipeline.git.branch >> ]
- equal: [ 'update-v8-snapshot-cache-on-develop', << pipeline.git.branch >> ]
- equal: [ 'fix/preflight', << pipeline.git.branch >> ]
- equal: [ 'ryanm/feat/unify-cdp-approach-in-electron', << pipeline.git.branch >> ]
- matches:
pattern: /^release\/\d+\.\d+\.\d+$/
value: << pipeline.git.branch >>
executors:
# the Docker image with Cypress dependencies and Chrome browser
cy-doc:
@@ -139,7 +139,7 @@ commands:
- run:
name: Check current branch to persist artifacts
command: |
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "emily/before-spec-promise" && "$CIRCLE_BRANCH" != "update-v8-snapshot-cache-on-develop" ]]; then
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "release/"* && "$CIRCLE_BRANCH" != "update-v8-snapshot-cache-on-develop" && "$CIRCLE_BRANCH" != "ryanm/feat/unify-cdp-approach-in-electron" ]]; then
echo "Not uploading artifacts or posting install comment for this branch."
circleci-agent step halt
fi
@@ -495,6 +495,7 @@ commands:
if [[ -v MAIN_RECORD_KEY ]]; then
# internal PR
CYPRESS_RECORD_KEY=$MAIN_RECORD_KEY \
CYPRESS_INTERNAL_ENABLE_TELEMETRY="true" \
yarn cypress:run --record --parallel --group 5x-driver-<<parameters.browser>> --browser <<parameters.browser>>
else
# external PR
@@ -567,6 +568,7 @@ commands:
PERCY_PARALLEL_NONCE=$CIRCLE_WORKFLOW_WORKSPACE_ID \
PERCY_ENABLE=${PERCY_TOKEN:-0} \
PERCY_PARALLEL_TOTAL=-1 \
CYPRESS_INTERNAL_ENABLE_TELEMETRY="true" \
$cmd yarn workspace @packages/<<parameters.package>> cypress:run:<<parameters.type>> --browser <<parameters.browser>> --record --parallel --group <<parameters.package>>-<<parameters.type>>
else
# external PR
@@ -606,8 +608,6 @@ commands:
fi
- store_test_results:
path: /tmp/cypress
- store_artifacts:
path: ./packages/<<parameters.package>>/cypress/videos
- store-npm-logs
run-system-tests:
@@ -1307,7 +1307,7 @@ jobs:
<<: *defaultsParameters
steps:
- restore_cached_workspace
- run:
- run:
name: 'Determine if Release Workflow should be triggered'
command: |
if [[ "$CIRCLE_BRANCH" != "develop" ]]; then
@@ -1455,7 +1455,7 @@ jobs:
# run type checking for each individual package
- run: yarn lerna run types
- verify-mocha-results:
expectedResultCount: 18
expectedResultCount: 19
- store_test_results:
path: /tmp/cypress
# CLI tests generate HTML files with sample CLI command output
@@ -5,7 +5,7 @@ on:
debug-only:
description: 'debug-only'
required: false
default: true
default: false
days-before-stale:
description: 'days-before-stale'
required: false
@@ -17,11 +17,11 @@ on:
exempt-issue-labels:
description: 'exempt-issue-labels'
required: false
default: 'type: feature,type: enhancement,routed-to-e2e,routed-to-ct,routed-to-tools,routed-to-cloud,Popular Issue'
default: 'type: feature,type: enhancement,routed-to-e2e,routed-to-ct,routed-to-tools,routed-to-cloud,prevent-stale,triaged'
exempt-pr-labels:
description: 'exempt-pr-labels'
required: false
default: 'type: feature,type: enhancement,Popular Issue'
default: 'type: feature,type: enhancement,prevent-stale,triaged'
schedule:
- cron: '30 1 * * *'
permissions:
@@ -31,8 +31,8 @@ env:
DEFAULT_DEBUG_ONLY: true
DEFAULT_DAYS_BEFORE_STALE: 180
DEFAULT_DAYS_BEFORE_CLOSE: 14
DEFAULT_EXEMPT_ISSUE_LABELS: 'type: feature,type: enhancement,routed-to-e2e,routed-to-ct,routed-to-tools,routed-to-cloud,Popular Issue'
DEFAULT_EXEMPT_PR_LABELS: 'type: feature,type: enhancement,Popular Issue'
DEFAULT_EXEMPT_ISSUE_LABELS: 'type: feature,type: enhancement,routed-to-e2e,routed-to-ct,routed-to-tools,routed-to-cloud,prevent-stale,triaged'
DEFAULT_EXEMPT_PR_LABELS: 'type: feature,type: enhancement,prevent-stale,triaged'
jobs:
stale:
runs-on: ubuntu-latest
@@ -50,5 +50,5 @@ jobs:
exempt-issue-labels: ${{ github.event.inputs.exempt-issue-labels || env.DEFAULT_EXEMPT_ISSUE_LABELS }}
exempt-pr-labels: ${{ github.event.inputs.exempt-pr-labels || env.DEFAULT_EXEMPT_PR_LABELS }}
exempt-all-milestones: true
operations-per-run: 1000 #using during debug mode to capture all the tickets impacted
operations-per-run: 400 #keeping this a bit higher because it processes newest tickets to oldest
debug-only: ${{ github.event.inputs.debug-only || env.DEFAULT_DEBUG_ONLY }}
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
echo 'IS_COLLABORATOR='$(jq -r '.data.repository.collaborators.totalCount' collaborators.json) >> $GITHUB_ENV
- uses: actions/add-to-project@v0.4.0
# only add issues/prs from outside contributors to the project
if: ${{ env.IS_COLLABORATOR == 0 }}
if: ${{ env.IS_COLLABORATOR == 0 || github.event.repository.name == 'cypress-support-internal' || github.event.pull_request.user.login == 'github-actions[bot]' || github.event.issue.user.login == 'github-actions[bot]' }}
with:
project-url: https://github.com/orgs/${{github.repository_owner}}/projects/${{env.PROJECT_NUMBER}}
github-token: ${{ secrets.ADD_TO_TRIAGE_BOARD_TOKEN }}
@@ -30,7 +30,7 @@ jobs:
strategy:
max-parallel: 1
matrix:
platform: [ubuntu-latest, windows-latest, macos-latest]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
env:
CYPRESS_BOT_APP_ID: ${{ secrets.RAM_APP }}
+1 -2
View File
@@ -59,8 +59,7 @@
"viewports",
"vite",
"vitejs",
"vueuse",
"Windi"
"vueuse"
],
"ignoreWords": [],
"import": []
+3 -3
View File
@@ -22,9 +22,9 @@
// Description: Adds syntax highlighting for all gql tags.
"apollographql.vscode-apollo",
// Name: WindiCSS Intellisense
// Description: Automatically sorts your WindiCSS classes.
"voorjaar.windicss-intellisense",
// Name: TailwindCSS Intellisense
// Description: Automatically sorts your TailwindCSS classes.
"bradlc.vscode-tailwindcss",
// Name: Volar
// Description: Language server for Vue. Required for any syntax highlighting in Vue files.
-3
View File
@@ -23,9 +23,6 @@
},
"typescript.tsdk": "node_modules/typescript/lib",
// A flag that controls whether or not Windi CSS classes will be sorted on save on save.
"windicss.sortOnSave": true,
// Support autocompletion and preview of strings.
// Additionally, support extraction of hardcoded strings into key-values.
"i18n-ally.localesPaths": "packages/frontend-shared/src/locales",
+8 -8
View File
@@ -42,7 +42,7 @@ Thanks for taking the time to contribute! :smile:
## Code of Conduct
All contributors are expecting to abide by our [Code of Conduct](./CODE_OF_CONDUCT.md).
All contributors are expected to abide by our [Code of Conduct](./CODE_OF_CONDUCT.md).
## Opening Issues
@@ -112,7 +112,7 @@ video | Problems with video recordings | [open](https://github.com/cypress-io/cy
## Writing Documentation
Cypress documentation lives in a separate repository with its own dependencies and build tools.
See [Documentation Contributing Guidelines](https://github.com/cypress-io/cypress-documentation/blob/master/CONTRIBUTING.md).
@@ -155,7 +155,7 @@ Here is a list of the core packages in this repository with a short description,
| [proxy](./packages/proxy) | `@packages/proxy` | Code for Cypress' network proxy layer. |
| [reporter](./packages/reporter) | `@packages/reporter` | The reporter shows the running results of the tests (The Command Log UI). |
| [resolve-dist](./packages/resolve-dist) | `@packages/resolve-dist` | Centralizes the resolution of paths to compiled/static assets from server-side code.. |
| [rewriter](./packages/rewriter) | `@packages/rewriter` | The logic to rewrite JS and HTML that flows through the Cypress proxy.
| [rewriter](./packages/rewriter) | `@packages/rewriter` | The logic to rewrite JS and HTML that flows through the Cypress proxy.
| [root](./packages/root) | `@packages/root` | Dummy package pointing at the root of the repository. |
| [runner](./packages/runner) | `@packages/runner` | (deprecated) The runner is the minimal "chrome" around the user's application under test. |
| [scaffold-config](./packages/scaffold-config) | `@packages/scaffold-config` | The logic related to scaffolding new projects using launchpad. |
@@ -318,13 +318,13 @@ Each package is responsible for building itself and testing itself and can do so
When executing top or package level scripts, [Vite](https://vitejs.dev/) may be used to build/host parts of the application. This section is to serve as a general reference for these environment variables that may be leverage throughout the repository.
###### `CYPRESS_INTERNAL_VITE_DEV`
Set to `1` if wanting to leverage [vite's](https://vitejs.dev/guide/#command-line-interface) `vite dev` over `vite build` to avoid a full [production build](https://vitejs.dev/guide/build.html).
###### `CYPRESS_INTERNAL_VITE_INSPECT`
###### `CYPRESS_INTERNAL_VITE_INSPECT`
Used internally to leverage [vite-plugin-inspect](https://github.com/antfu/vite-plugin-inspect) to view intermediary vite plugin state. The `CYPRESS_INTERNAL_VITE_DEV` is required for this to be applied correctly. Set to `1` to enable.
###### `CYPRESS_INTERNAL_VITE_OPEN_MODE_TESTING`
###### `CYPRESS_INTERNAL_VITE_OPEN_MODE_TESTING`
Leveraged only for internal cy-in-cy type tests to access the Cypress instance from the parent frame. Please see the [E2E Open Mode Testing](./guides/e2e-open-testing.md) Guide. Set to `true` when doing
###### `CYPRESS_INTERNAL_VITE_APP_PORT`
###### `CYPRESS_INTERNAL_VITE_APP_PORT`
Leveraged only when `CYPRESS_INTERNAL_VITE_DEV` is set to spawn the vite dev server for the app on the specified port. The default port is `3333`.
###### `CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT`
###### `CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT`
Leveraged only when `CYPRESS_INTERNAL_VITE_DEV` is set to spawn the vite dev server for the launchpad on the specified port. The default port is `3001`.
#### Debug Logs
@@ -443,7 +443,7 @@ We do not continuously deploy the Cypress binary, so `develop` contains all of t
- After the PR is approved, the original contributor can merge the PR (if the original contributor has access).
- When you merge a PR into `develop`, select [**Squash and merge**](https://docs.github.com/en/github/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/about-pull-request-merges#squash-and-merge-your-pull-request-commits). This will squash all commits into a single commit.
*The only exceptions to squashing are:*
*The only exceptions to squashing are:*
1. When converting files to another language and there is a clear commit history needed to maintain from the file conversion.
2. When merging a `release/*` branch to `develop`. Individual PRs were already squashed when they were merged to the release branch, and we want that history intact on develop.
+2 -2
View File
@@ -1,5 +1,5 @@
{
"chrome:beta": "112.0.5615.49",
"chrome:stable": "111.0.5563.146",
"chrome:beta": "113.0.5672.24",
"chrome:stable": "112.0.5615.49",
"chrome:minimum": "64.0.3282.0"
}
+48 -6
View File
@@ -7,22 +7,64 @@ _Released 03/1/2023 (PENDING)_
- The [`cy.readFile()`](/api/commands/readfile) command is now retry-able as a [query command](https://on.cypress.io/retry-ability). This should not affect any tests using it; the functionality is unchanged. However, it can no longer be overwritten using [`Cypress.Commands.overwrite()`](/api/cypress-api/custom-commands#Overwrite-Existing-Commands). Addressed in [#25595](https://github.com/cypress-io/cypress/pull/25595).
## 12.9.1
## 12.11.1
_Released 04/11/2023 (PENDING)_
_Released 05/09/2023 (PENDING)_
**Bugfixes:**
- Fixed an issue in Electron where devtools gets out of sync with the DOM occasionally. Addresses [#15932](https://github.com/cypress-io/cypress/issues/15932).
- Updated the Chromium renderer process crash message to be more terse. Addressed in [#26597](https://github.com/cypress-io/cypress/pull/26597).
## 12.11.0
_Released 04/26/2023_
**Features:**
- Adds Component Testing support for Angular 16. Addresses [#26044](https://github.com/cypress-io/cypress/issues/26044).
- The run navigation component on the [Debug page](https://on.cypress.io/debug-page) will now display a warning message if there are more relevant runs than can be displayed in the list. Addresses [#26288](https://github.com/cypress-io/cypress/issues/26288).
**Bugfixes:**
- Fixed an issue where setting `videoCompression` to `0` would cause the video output to be broken. `0` is now treated as false. Addresses [#5191](https://github.com/cypress-io/cypress/issues/5191) and [#24595](https://github.com/cypress-io/cypress/issues/24595).
- Fixed an issue on the [Debug page](https://on.cypress.io/debug-page) where the passing run status would appear even if the Cypress Cloud organization was over its monthly test result limit. Addresses [#26528](https://github.com/cypress-io/cypress/issues/26528).
**Misc:**
- Cleaned up our open telemetry dependencies, reducing the size of the open telemetry modules. Addressed in [#26522](https://github.com/cypress-io/cypress/pull/26522).
**Dependency Updates:**
- Upgraded [`vue`](https://www.npmjs.com/package/vue) from `3.2.31` to `3.2.47`. Addressed in [#26555](https://github.com/cypress-io/cypress/pull/26555).
## 12.10.0
_Released 04/17/2023_
**Features:**
- The Component Testing setup wizard will now show a warning message if an issue is encountered with an installed [third party framework definition](https://on.cypress.io/component-integrations). Addresses [#25838](https://github.com/cypress-io/cypress/issues/25838).
**Bugfixes:**
- Capture the [Azure](https://azure.microsoft.com/) CI provider's environment variable [`SYSTEM_PULLREQUEST_PULLREQUESTNUMBER`](https://learn.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&tabs=yaml#system-variables-devops-services) to display the linked PR number in the Cloud. Addressed in [#26215](https://github.com/cypress-io/cypress/pull/26215).
- Fixed an issue in the onboarding wizard where project framework & bundler would not be auto-detected when opening directly into component testing mode using the `--component` CLI flag. Fixes [#22777](https://github.com/cypress-io/cypress/issues/22777).
- Fixed an issue in the onboarding wizard where project framework & bundler would not be auto-detected when opening directly into component testing mode using the `--component` CLI flag. Fixes [#22777](https://github.com/cypress-io/cypress/issues/22777) and [#26388](https://github.com/cypress-io/cypress/issues/26388).
- Updated to use the `SEMAPHORE_GIT_WORKING_BRANCH` [Semphore](https://docs.semaphoreci.com) CI environment variable to correctly associate a Cloud run to the current branch. Previously this was incorrectly associating a run to the target branch. Fixes [#26309](https://github.com/cypress-io/cypress/issues/26309).
- Fix an edge case in Component Testing where a custom `baseUrl` in `tsconfig.json` for Next.js 13.2.0+ is not respected. This was partially fixed in [#26005](https://github.com/cypress-io/cypress/pull/26005), but an edge case was missed. Fixes [#25951](https://github.com/cypress-io/cypress/issues/25951).
- Correctly detect and resolve dependencies when configuring Component Testing in projects using Yarn's [Plug'n'Play feature](https://yarnpkg.com/features/pnp). Fixes [#25960](https://github.com/cypress-io/cypress/issues/25960).
- Fixed an issue where `click` events fired on `.type('{enter}')` did not propagate through shadow roots. Fixes [#26392](https://github.com/cypress-io/cypress/issues/26392).
**Misc:**
- Removed unintentional debug logs. Addressed in [#26411](https://github.com/cypress-io/cypress/pull/26411).
- Improved styling on the [Runs Page](https://docs.cypress.io/guides/core-concepts/cypress-app#Runs). Addresses [#26180](https://github.com/cypress-io/cypress/issues/26180).
**Dependency Updates:**
- Upgraded [`commander`](https://www.npmjs.com/package/commander) from `^5.1.0` to `^6.2.1`. Addressed in [#26226](https://github.com/cypress-io/cypress/pull/26226).
- Upgraded [`minimist`](https://www.npmjs.com/package/minimist) from `1.2.6` to `1.2.8` to address this [CVE-2021-44906](https://github.com/advisories/GHSA-xvch-5gv4-984h) NVD security vulnerability. Addressed in [#26254](https://github.com/cypress-io/cypress/pull/26254).
**Misc:**
- Removed unintentional debug logs. Address in [#26411](https://github.com/cypress-io/cypress/pull/26411)
## 12.9.0
_Released 03/28/2023_
+5 -19
View File
@@ -218,7 +218,7 @@ exports['cli help command shows help 1'] = `
Commands:
help Shows CLI help and exits
version prints Cypress version
version [options] prints Cypress version
open [options] Opens Cypress in the interactive GUI.
run [options] Runs Cypress tests from the CLI without the GUI
open-ct [options] Opens Cypress component testing interactive mode.
@@ -258,7 +258,7 @@ exports['cli help command shows help for -h 1'] = `
Commands:
help Shows CLI help and exits
version prints Cypress version
version [options] prints Cypress version
open [options] Opens Cypress in the interactive GUI.
run [options] Runs Cypress tests from the CLI without the GUI
open-ct [options] Opens Cypress component testing interactive mode.
@@ -298,7 +298,7 @@ exports['cli help command shows help for --help 1'] = `
Commands:
help Shows CLI help and exits
version prints Cypress version
version [options] prints Cypress version
open [options] Opens Cypress in the interactive GUI.
run [options] Runs Cypress tests from the CLI without the GUI
open-ct [options] Opens Cypress component testing interactive mode.
@@ -339,7 +339,7 @@ exports['cli unknown command shows usage and exits 1'] = `
Commands:
help Shows CLI help and exits
version prints Cypress version
version [options] prints Cypress version
open [options] Opens Cypress in the interactive GUI.
run [options] Runs Cypress tests from the CLI without the GUI
open-ct [options] Opens Cypress component testing interactive mode.
@@ -408,20 +408,6 @@ Electron version: not found
Bundled Node version: not found
`
exports['cli --version no binary version 1'] = `
Cypress package version: 1.2.3
Cypress binary version: not installed
Electron version: not found
Bundled Node version: not found
`
exports['cli -v no binary version 1'] = `
Cypress package version: 1.2.3
Cypress binary version: not installed
Electron version: not found
Bundled Node version: not found
`
exports['cli cypress run warns with space-separated --spec 1'] = `
⚠ Warning: It looks like you're passing --spec a space-separated list of arguments:
@@ -466,7 +452,7 @@ exports['cli CYPRESS_INTERNAL_ENV allows and warns when staging environment 1']
Commands:
help Shows CLI help and exits
version prints Cypress version
version [options] prints Cypress version
open [options] Opens Cypress in the interactive GUI.
run [options] Runs Cypress tests from the CLI without the GUI
open-ct [options] Opens Cypress component testing interactive mode.
+17 -21
View File
@@ -154,26 +154,16 @@ const text = (description) => {
function includesVersion (args) {
return (
_.includes(args, 'version') ||
_.includes(args, '--version') ||
_.includes(args, '-v')
)
}
function showVersions (args) {
function showVersions (opts) {
debug('printing Cypress version')
debug('additional arguments %o', args)
debug('additional arguments %o', opts)
const versionParser = commander.option(
'--component <package|binary|electron|node>', 'component to report version for',
)
.allowUnknownOption(true)
const parsed = versionParser.parse(args)
const parsedOptions = {
component: parsed.component,
}
debug('parsed version arguments %o', parsedOptions)
debug('parsed version arguments %o', opts)
const reportAllVersions = (versions) => {
logger.always('Cypress package version:', versions.package)
@@ -215,8 +205,8 @@ function showVersions (args) {
return require('./exec/versions')
.getVersions()
.then((versions = defaultVersions) => {
if (parsedOptions.component) {
reportComponentVersion(parsedOptions.component, versions)
if (opts?.component) {
reportComponentVersion(opts.component, versions)
} else {
reportAllVersions(versions)
}
@@ -456,13 +446,19 @@ module.exports = {
program.help()
})
program
const handleVersion = (cmd) => {
return cmd
.option('--component <package|binary|electron|node>', 'component to report version for')
.action((opts, ...other) => {
showVersions(util.parseOpts(opts))
})
}
handleVersion(program
.storeOptionsAsProperties()
.option('-v, --version', text('version'))
.command('version')
.description(text('version'))
.action(() => {
showVersions(args)
})
.description(text('version')))
maybeAddInspectFlags(addCypressOpenCommand(program))
.action((opts) => {
@@ -665,7 +661,7 @@ module.exports = {
// and now does not understand top level options
// .option('-v, --version').command('version')
// so we have to manually catch '-v, --version'
return showVersions(args)
handleVersion(program)
}
debug('program parsing arguments')
+1 -2
View File
@@ -34,7 +34,7 @@
"check-more-types": "^2.24.0",
"cli-cursor": "^3.1.0",
"cli-table3": "~0.6.1",
"commander": "^5.1.0",
"commander": "^6.2.1",
"common-tags": "^1.8.0",
"dayjs": "^1.10.4",
"debug": "^4.3.4",
@@ -89,7 +89,6 @@
"mock-fs": "5.1.1",
"mocked-env": "1.3.2",
"nock": "13.2.9",
"postinstall-postinstall": "2.1.0",
"proxyquire": "2.1.3",
"resolve-pkg": "2.0.0",
"shelljs": "0.8.5",
+128 -148
View File
@@ -23,11 +23,11 @@ describe('cli', () => {
beforeEach(() => {
logger.reset()
sinon.stub(process, 'exit')
sinon.stub(process, 'exit').returns(null)
os.platform.returns('darwin')
// sinon.stub(util, 'exit')
sinon.stub(util, 'logErrorExit1')
sinon.stub(util, 'logErrorExit1').returns(null)
sinon.stub(util, 'pkgBuildInfo').returns({ stable: true })
this.exec = (args) => {
const cliArgs = `node test ${args}`.split(' ')
@@ -136,189 +136,169 @@ describe('cli', () => {
})
})
context('cypress version', () => {
let restoreEnv
;['--version', '-v', 'version'].forEach((versionCommand) => {
context(`cypress ${versionCommand}`, () => {
let restoreEnv
afterEach(() => {
if (restoreEnv) {
restoreEnv()
restoreEnv = null
}
})
afterEach(() => {
if (restoreEnv) {
restoreEnv()
restoreEnv = null
}
})
const binaryDir = '/binary/dir'
const binaryDir = '/binary/dir'
beforeEach(() => {
sinon.stub(state, 'getBinaryDir').returns(binaryDir)
})
describe('individual package versions', () => {
beforeEach(() => {
sinon.stub(state, 'getBinaryDir').returns(binaryDir)
})
describe('individual package versions', () => {
beforeEach(() => {
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon
.stub(state, 'getBinaryPkgAsync')
.withArgs(binaryDir)
.resolves({
version: 'X.Y.Z',
electronVersion: '10.9.8',
electronNodeVersion: '7.7.7',
})
})
it('reports just the package version', (done) => {
this.exec(`${versionCommand} --component package`)
process.exit.callsFake((exitCode) => {
expect(logger.print()).to.equal('1.2.3')
done()
})
})
it('reports just the binary version', (done) => {
this.exec(`${versionCommand} --component binary`)
process.exit.callsFake(() => {
expect(logger.print()).to.equal('X.Y.Z')
done()
})
})
it('reports just the electron version', (done) => {
this.exec(`${versionCommand} --component electron`)
process.exit.callsFake(() => {
expect(logger.print()).to.equal('10.9.8')
done()
})
})
it('reports just the bundled Node version', (done) => {
this.exec(`${versionCommand} --component node`)
process.exit.callsFake(() => {
expect(logger.print()).to.equal('7.7.7')
done()
})
})
it('handles not found bundled Node version', (done) => {
state.getBinaryPkgAsync
.withArgs(binaryDir)
.resolves({
version: 'X.Y.Z',
electronVersion: '10.9.8',
})
this.exec(`${versionCommand} --component node`)
process.exit.callsFake(() => {
expect(logger.print()).to.equal('not found')
done()
})
})
})
it('reports package version', (done) => {
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon
.stub(state, 'getBinaryPkgAsync')
.withArgs(binaryDir)
.resolves({
version: 'X.Y.Z',
electronVersion: '10.9.8',
electronNodeVersion: '7.7.7',
})
})
it('reports just the package version', (done) => {
this.exec('version --component package')
this.exec(versionCommand)
process.exit.callsFake(() => {
expect(logger.print()).to.equal('1.2.3')
snapshot('cli version and binary version 1', logger.print(), { allowSharedSnapshot: true })
done()
})
})
it('reports just the binary version', (done) => {
this.exec('version --component binary')
it('reports package and binary message', (done) => {
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' })
this.exec(versionCommand)
process.exit.callsFake(() => {
expect(logger.print()).to.equal('X.Y.Z')
snapshot('cli version and binary version 2', logger.print(), { allowSharedSnapshot: true })
done()
})
})
it('reports just the electron version', (done) => {
this.exec('version --component electron')
process.exit.callsFake(() => {
expect(logger.print()).to.equal('10.9.8')
done()
})
})
it('reports just the bundled Node version', (done) => {
this.exec('version --component node')
process.exit.callsFake(() => {
expect(logger.print()).to.equal('7.7.7')
done()
})
})
it('handles not found bundled Node version', (done) => {
state.getBinaryPkgAsync
.withArgs(binaryDir)
.resolves({
it('reports electron and node message', (done) => {
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon.stub(state, 'getBinaryPkgAsync').resolves({
version: 'X.Y.Z',
electronVersion: '10.9.8',
electronVersion: '10.10.88',
electronNodeVersion: '11.10.3',
})
this.exec('version --component node')
this.exec(versionCommand)
process.exit.callsFake(() => {
expect(logger.print()).to.equal('not found')
snapshot('cli version with electron and node 1', logger.print(), { allowSharedSnapshot: true })
done()
})
})
})
it('reports package version', (done) => {
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon
.stub(state, 'getBinaryPkgAsync')
.withArgs(binaryDir)
.resolves({
version: 'X.Y.Z',
it('reports package and binary message with npm log silent', (done) => {
restoreEnv = mockedEnv({
npm_config_loglevel: 'silent',
})
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' })
this.exec(versionCommand)
process.exit.callsFake(() => {
// should not be empty!
snapshot('cli version and binary version with npm log silent', logger.print(), { allowSharedSnapshot: true })
done()
})
})
this.exec('version')
process.exit.callsFake(() => {
snapshot('cli version and binary version 1', logger.print())
done()
})
})
it('reports package and binary message with npm log warn', (done) => {
restoreEnv = mockedEnv({
npm_config_loglevel: 'warn',
})
it('reports package and binary message', (done) => {
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' })
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon.stub(state, 'getBinaryPkgAsync').resolves({
version: 'X.Y.Z',
})
this.exec('version')
process.exit.callsFake(() => {
snapshot('cli version and binary version 2', logger.print())
done()
})
})
it('reports electron and node message', (done) => {
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon.stub(state, 'getBinaryPkgAsync').resolves({
version: 'X.Y.Z',
electronVersion: '10.10.88',
electronNodeVersion: '11.10.3',
})
this.exec('version')
process.exit.callsFake(() => {
snapshot('cli version with electron and node 1', logger.print())
done()
})
})
it('reports package and binary message with npm log silent', (done) => {
restoreEnv = mockedEnv({
npm_config_loglevel: 'silent',
})
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon.stub(state, 'getBinaryPkgAsync').resolves({ version: 'X.Y.Z' })
this.exec('version')
process.exit.callsFake(() => {
this.exec(versionCommand)
process.exit.callsFake(() => {
// should not be empty!
snapshot('cli version and binary version with npm log silent', logger.print())
done()
})
})
it('reports package and binary message with npm log warn', (done) => {
restoreEnv = mockedEnv({
npm_config_loglevel: 'warn',
snapshot('cli version and binary version with npm log warn', logger.print(), { allowSharedSnapshot: true })
done()
})
})
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon.stub(state, 'getBinaryPkgAsync').resolves({
version: 'X.Y.Z',
})
it('handles non-existent binary', (done) => {
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon.stub(state, 'getBinaryPkgAsync').resolves(null)
this.exec('version')
process.exit.callsFake(() => {
// should not be empty!
snapshot('cli version and binary version with npm log warn', logger.print())
done()
})
})
it('handles non-existent binary version', (done) => {
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon.stub(state, 'getBinaryPkgAsync').resolves(null)
this.exec('version')
process.exit.callsFake(() => {
snapshot('cli version no binary version 1', logger.print())
done()
})
})
it('handles non-existent binary --version', (done) => {
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon.stub(state, 'getBinaryPkgAsync').resolves(null)
this.exec('--version')
process.exit.callsFake(() => {
snapshot('cli --version no binary version 1', logger.print())
done()
})
})
it('handles non-existent binary -v', (done) => {
sinon.stub(util, 'pkgVersion').returns('1.2.3')
sinon.stub(state, 'getBinaryPkgAsync').resolves(null)
this.exec('-v')
process.exit.callsFake(() => {
snapshot('cli -v no binary version 1', logger.print())
done()
this.exec(versionCommand)
process.exit.callsFake(() => {
snapshot('cli version no binary version 1', logger.print(), { allowSharedSnapshot: true })
done()
})
})
})
})
+2
View File
@@ -1,3 +1,5 @@
# [create-cypress-tests-v2.0.2](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v2.0.1...create-cypress-tests-v2.0.2) (2023-04-07)
# [create-cypress-tests-v2.0.1](https://github.com/cypress-io/cypress/compare/create-cypress-tests-v2.0.0...create-cypress-tests-v2.0.1) (2023-01-03)
+1 -1
View File
@@ -20,7 +20,7 @@
"bluebird": "3.7.2",
"chalk": "4.1.0",
"cli-highlight": "2.1.10",
"commander": "6.1.0",
"commander": "6.2.1",
"fast-glob": "3.2.7",
"find-up": "5.0.0",
"fs-extra": "^9.1.0",
+1 -3
View File
@@ -19,8 +19,6 @@ All other tests will be marked pending, see why in the [Cypress test statuses](h
If you have multiple spec files, all specs will be loaded, and every test will be filtered the same way, since the grep is run-time operation and cannot eliminate the spec files without loading them. If you want to run only specific tests, use the built-in [--spec](https://on.cypress.io/command-line#cypress-run-spec-lt-spec-gt) CLI argument.
Watch the video [intro to @cypress/grep plugin](https://www.youtube.com/watch?v=HS-Px-Sghd8)
Table of Contents
<!-- MarkdownTOC autolink="true" -->
@@ -602,4 +600,4 @@ Version >= 3 of @cypress/grep _only_ supports Cypress >= 10.
License: MIT - do anything with the code, but don't blame me if it does not work.
Support: if you find any problems with this module, email / tweet /
[open issue](https://github.com/cypress-io/cypress/issues) on Github.
[open issue](https://github.com/cypress-io/cypress/issues) on Github.
+3 -3
View File
@@ -18,7 +18,7 @@
"devDependencies": {
"@cypress/mount-utils": "0.0.0-development",
"@types/semver": "7.3.9",
"@vitejs/plugin-react": "1.3.1",
"@vitejs/plugin-react": "4.0.0",
"axios": "0.21.2",
"cypress": "0.0.0-development",
"prop-types": "15.7.2",
@@ -28,8 +28,8 @@
"react-router-dom": "6.0.0-alpha.1",
"semver": "^7.3.2",
"typescript": "^4.7.4",
"vite": "4.1.4",
"vite-plugin-require-transform": "1.0.3"
"vite": "4.3.2",
"vite-plugin-require-transform": "1.0.12"
},
"peerDependencies": {
"@types/react": "^16.9.16 || ^17.0.0",
+2 -2
View File
@@ -27,8 +27,8 @@
"mocha": "^9.2.2",
"sinon": "^13.0.1",
"ts-node": "^10.9.1",
"vite": "4.1.4",
"vite-plugin-inspect": "0.4.3"
"vite": "4.3.2",
"vite-plugin-inspect": "0.7.24"
},
"files": [
"dist",
+5 -1
View File
@@ -80,7 +80,11 @@ function makeCypressViteConfig (config: ViteDevServerConfig, vite: Vite): Inline
paths: [projectRoot],
})))
const viteConfig: InlineConfig = {
// Our Vite typings do not have the 'incremental' field since it was removed in 4.2, but users' version
// of Vite may be older and we want to use it if it's there
type Vite_4_1_Config = { optimizeDeps: { esbuildOptions: { incremental?: boolean } } }
const viteConfig: InlineConfig & Vite_4_1_Config = {
root: projectRoot,
base: `${devServerPublicPathRoute}/`,
optimizeDeps: {
@@ -0,0 +1,5 @@
**/dist
**/*.d.ts
**/package-lock.json
**/tsconfig.json
**/cypress/fixtures
+32
View File
@@ -0,0 +1,32 @@
{
"plugins": [
"cypress",
"@cypress/dev"
],
"extends": [
"plugin:@cypress/dev/general",
"plugin:@cypress/dev/tests",
"plugin:@cypress/dev/react"
],
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser"
},
"env": {
"cypress/globals": true
},
"rules": {
"no-console": "off",
"mocha/no-global-tests": "off",
"react/jsx-filename-extension": [
"warn",
{
"extensions": [
".js",
".jsx",
".tsx"
]
}
]
}
}
+1
View File
@@ -0,0 +1 @@
cypress/videos/*
+123
View File
@@ -0,0 +1,123 @@
# @cypress/vite-plugin-cypress-esm
A Vite plugin that intercepts and rewrites ES module imports within [Cypress component tests](https://docs.cypress.io/guides/component-testing/overview). The [ESM specification](https://tc39.es/ecma262/#sec-modules) generates modules that are "sealed", requiring the runtime (the browser) to prevent any alteration to the module namespace. While this has security and performance benefits, it prevents use of mocking libraries which would need to replace namespace members. This plugin wraps modules in a special [`Proxy`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) implementation, allowing for instrumentation by libraries such as Sinon.
> **Note:** This package is a pre-release alpha and is not yet stable. There are likely to be bugs and edge cases. Please report any bugs [here](https://github.com/cypress-io/cypress/issues/new?labels=npm:%20@cypress/vite-plugin-cypress-esm). [Learn more about Cypress release stages](https://docs.cypress.io/guides/references/release-stages#Alpha) and expectations around stability.
## Debugging
Run Cypress with `DEBUG=cypress:vite-plugin-cypress-esm`. You will get logs in the terminal, for the code transformation, and in the browser console, for intercepting and wrapping the modules in a Proxy.
## Compatibility
| @cypress/vite-plugin-mock-esm | cypress |
| ------------------------ | ------- |
| >= v1 | >= v12 |
## Usage
This plugin rewrites the ES modules served by Vite to make them mutable and therefore compatible with methods like [`cy.spy()`](https://docs.cypress.io/api/commands/spy) and [`cy.stub()`](https://docs.cypress.io/api/commands/stub) that require modifying otherwise-sealed objects. Since this is a testing-specific plugin it is recommended to apply it your Vite config only when running your Cypress tests. One way to do so would be in `cypress.config`:
```ts
import { defineConfig } from 'cypress'
import viteConfig from './vite.config'
import { mergeConfig } from 'vite'
import { CypressEsm } from '@cypress/vite-plugin-cypress-esm'
export default defineConfig({
component: {
devServer: {
bundler: 'vite',
framework: 'react',
viteConfig: () => {
return mergeConfig(
viteConfig,
{
plugins: [
CypressEsm(),
]
}
),
}
},
}
})
```
### `ignoreList`
Some modules may be incompatible with Proxy-based implementation. The eventual goal is to support wrapping all modules in a Proxy to better facilitate testing. For now, if you run into any issues with a particular module, you can add it to the `ignoreList` like so:
```ts
CypressEsm({
ignoreList: ['react-router', 'react-router-dom']
})
```
You can also use a glob, which uses [`picomatch`](https://github.com/micromatch/picomatch) internally:
```ts
CypressEsm({
ignoreList: ['*react*']
})
```
React is known to have some conflicts with the Proxy implementation that cause problems stubbing internal React functionality. Since it is unlikely you want to stub parts of React itself, it's a good idea to add it to the `ignoreList`.
## Known Issues
### Import Syntax
All known [import syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) is supported, however there may edge cases that have not been identified.
### Regular Expression matching
This module uses Regular Expression matching to transform the modules on the server to facilitate wrapping them in a `Proxy` on the client. In future updates, a more robust AST-based approach will be explored. A limitation of the current approach is that it does not recognize syntax from actual code vs content found within strings (for instance, an error string that contains example code syntax). This can result in inappropriately modified string constants.
### Auto-hosting
ESM imports are automatically hoisted to the top of a given module so they happen first before any code that references them. This plugin does not currently perform any hoisting, so imports are transformed to variable references in place. If you have code that attempts to reference an imported value prior to that import it will likely break. This is a known issue with HMR logic in Svelte projects, and will typically present as a "use before define" error.
### Self-references and internal calls
This plugin works by intercepting calls coming *in* to a module. This will not work for situations where a module attempts to make *internal* calls to a function within the same module or directly compare against a function within the same module. Eg:
```js
// mod_1.js
export function foo () {
// ...
}
export function bar (mod) {
return mod === foo
}
// mod_2.js
import { foo, bar } from './mod_1.js'
bar(foo) //=> false
```
In this example, `bar(foo)` is passing a reference to `mod_1.foo`, where `mod_1` is a module wrapped in a `Proxy`. In the original `mod_1.js`, the reference to `foo` is the original, unwrapped `foo`, so the comparison return `false`. This may cause issues in some libraries, such as React Router when lazy loading routes. You can add modules to `ignoreList` to work around this issue.
### Sinon compatibility
This plugin is designed to work with [Sinon](https://sinonjs.org/) since that is what Cypress uses internally for `cy.stub` and `cy.spy` - attempting to utilize other stubbing/mocking libraries or directly mutating modules is not a supported use case and will likely not work as expected.
## Troubleshooting
This is an **_Alpha_** release, meaning there a very likely bugs in the implementation and it is expected that you will encounter issues. We appreciate any bug reports once you have performed the troubleshooting process below.
If you encounter issues:
1. Ensure you're using the very latest version of this Plugin and Cypress
2. Try temporarily removing this plugin from your test's Vite config - if the issue is still present then it is not related to this plugin.
3. Verify you have not encountered one of the [Known Issues](#known-issues)
3. If the issue disappeared then try narrowing down if it's related to a specific module/dependency by using the `ignoreList` config
4. If your problem isn't related to a specific dependency and can't be isolated please file a bug report [here](https://github.com/cypress-io/cypress/issues/new?labels=npm:%20@cypress/vite-plugin-cypress-esm). A reproduction case project is extremely helpful to track down specific issues, and capturing [Debug Logs](#debugging) from both your terminal *and* the browser devtools console is very helpful.
## License
[![license](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/cypress-io/cypress/blob/develop/LICENSE)
This project is licensed under the terms of the [MIT license](/LICENSE).
## [Changelog](./CHANGELOG.md)
@@ -0,0 +1,191 @@
const __cypressModuleCache = new Map()
const NO_REDEFINE_LIST = new Set(['prototype'])
let debug = false
function createProxyModule (module) {
// What we build our module proxy off of depends on whether the module has a default export
// We need to be able to support `import DefaultValue from 'module'` => `const DefaultValue = __cypressModule(module)`
const base = module.default || module
let target
// Work around for the fact that a module with a default export needs to work the same way via object destructuring
// for this module remapping concept to work
// ```
// import TheDefault from 'module'
// `TheDefault` could be an object or a function
// ```
if (typeof base === 'function') {
target = function (...params) {
if (typeof target.default === 'function') {
return target.default.apply(this, params)
}
if (typeof module === 'function') {
return module.apply(this, params)
}
}
} else {
target = {}
}
const proxies = {}
function redefinePropertyDescriptors (module, overrides) {
Object.entries(Object.getOwnPropertyDescriptors(module)).forEach(([key, descriptor]) => {
if (Array.isArray(module)) {
return
}
if (NO_REDEFINE_LIST.has(key)) {
log(`⏭️ Skipping ${key}`)
return
}
log(`🧪 Redefining ${key}`)
Object.defineProperty(target, key, {
...descriptor,
...overrides,
})
if (typeof descriptor.value === 'function') {
// This is how you can see if something is a class
// Playground: https://regex101.com/r/OS2Iyg/1
// Important! RegEx instances are stateful, do not extract to a constant
const isClass = /class.+?\{.+?\}/gms.test(descriptor.value.toString())
if (isClass) {
log(`🏗️ Handling ${key} as a constructor`)
proxies[key] = function (...params) {
// Edge case - use `apply` with `new` to create a class instance
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct
return Reflect.construct(target[key], params)
}
} else {
log(`🎁 Handling ${key} with a standard wrapper function`)
proxies[key] = function (...params) {
return target[key].apply(this, params)
}
}
proxies[key].prototype = target[key].prototype
}
})
}
// Do not proxify arrays - you can't spy on an array, no need.
if (Array.isArray(module.default)) {
return module.default
}
if (module.default && typeof module.default !== 'function') {
redefinePropertyDescriptors(module.default, {
writable: true,
enumerable: true,
})
}
redefinePropertyDescriptors(module, {
configurable: true,
writable: true,
})
const moduleProxy = new Proxy(target, {
get (_, prop, receiver) {
const value = target[prop]
if (typeof value === 'function') {
// Check to see if this retrieval is coming from a sinon `spy` creation
// If so, we want to supply the 'true' function rather than our proxied version
// so the spy can call through to the real implementation
const stack = new Error().stack
if (stack?.includes('Sandbox.spy')) {
log(`🕵️ Detected ${prop} is being defined as a Sinon spy`)
return value
}
// Otherwise, return our proxied function implementation
return proxies[prop]
}
return target[prop]
},
set (obj, prop, value) {
target[prop] = value
if (typeof value === 'function' && !(prop in proxies)) {
proxies[prop] = function (...params) {
return target[prop].apply(this, params)
}
}
return true
},
defineProperty (_, key, descriptor) {
// Ignore `define` attempts to set a sinon proxy, but return true anyways
// Allowing define would blow away our function proxy
// Sinon circles back and attempts to set via `set` anyways so this isn't necessary
if (descriptor.value?.isSinonProxy) {
return true
}
Object.defineProperty(target, key, { ...descriptor, writable: true, configurable: true })
return true
},
deleteProperty (_, prop) {
// Don't allow deletion - Sinon tries to delete things as a cleanup activity which breaks our proxied functions
return true
},
})
return moduleProxy
}
function log (msg) {
if (!debug) {
return
}
console.log(`[cypress:vite-plugin-mock-esm]: ${msg}`)
}
function cacheAndProxifyModule (id, module) {
if (__cypressModuleCache.has(module)) {
return __cypressModuleCache.get(module)
}
log(`🔨 creating proxy module for ${id}`)
const moduleProxy = createProxyModule(module)
log(`✅ created proxy module for ${id}`)
__cypressModuleCache.set(module, moduleProxy)
log(`📈 Module cache now contains ${__cypressModuleCache.size} entries`)
return moduleProxy
}
window.__cypressDynamicModule = function (id, importPromise, _debug = false) {
debug = _debug
return Promise.resolve(importPromise.then((module) => {
return cacheAndProxifyModule(id, module)
}))
}
window.__cypressModule = function (id, module, _debug = false) {
debug = _debug
return cacheAndProxifyModule(id, module)
}
@@ -0,0 +1,27 @@
import { defineConfig } from 'cypress'
import react from '@vitejs/plugin-react'
import { CypressEsm } from './src'
export default defineConfig({
projectId: 'ypt4pf',
component: {
supportFile: false,
specPattern: 'cypress/component/**/*.cy.ts*',
devServer: {
bundler: 'vite',
framework: 'react',
viteConfig: () => {
return {
plugins: [
react({
jsxRuntime: 'classic',
}),
CypressEsm({
ignoreList: ['*Immutable*', '*MyAsync*'],
}),
],
}
},
},
},
})
@@ -0,0 +1,6 @@
import './fixtures/style.css'
import { add } from './fixtures/add'
it('does not transform non JS assets', () => {
expect(add(1, 2)).to.eq(3)
})
@@ -0,0 +1,77 @@
/// <reference types="cypress" />
import _ from 'lodash'
describe('dynamic imports', () => {
it('uses real implementation', () => {
expect(_.add(1, 2)).to.eq(3)
})
it('mocks', () => {
cy.stub(_, 'add').callsFake(function multiply (a: number, b: number) {
return a * b
})
// Plot twist - add actually does multiplication!
expect(_.add(1, 2)).to.eq(2)
})
it('uses real implementation again', () => {
expect(_.add(1, 2)).to.eq(3)
})
it('stubs lodash method from node_modules using dynamic import', () => {
let done = false
async function run () {
const _ = await import('lodash')
cy.stub(_, 'camelCase').callsFake((str: string) => str.toUpperCase())
const result = _.camelCase('foo_bar')
expect(result).to.eq('FOO_BAR')
done = true
}
cy.wrap(run()).then(() => {
expect(done).to.be.true
})
})
it('stub local dynamic import', () => {
let called = false
async function run () {
const mod = await import('./fixtures/add')
cy.stub(mod, 'add')
mod.add(1, 2)
called = true
}
cy.wrap(run()).then(() => {
expect(called).to.be.true
})
})
it('stubs lodash method from node_modules using `then`', () => {
import('lodash').then((mod) => {
cy.stub(mod, 'camelCase').callsFake((str: string) => str.toUpperCase())
const result = mod.camelCase('foo_bar')
expect(result).to.eq('FOO_BAR')
})
})
it('destructures', () => {
async function run () {
const { add } = await import('lodash')
expect(add(1, 2)).to.eq(3)
}
cy.wrap(run())
})
})
@@ -0,0 +1,83 @@
/// <reference types="cypress" />
import React, { Suspense, lazy } from 'react'
import { mount } from 'cypress/react'
import * as Mod from './fixtures/class'
import * as Foo from './fixtures/Foo'
import numbers from './fixtures/defaultExportArray'
import { App } from './fixtures/reactQuery'
import { letters } from './fixtures/namedExportArray'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
const About = lazy(() => import('./fixtures/exportDefaultConst'))
describe('edge cases', () => {
describe('class with constructor', () => {
context('named export', () => {
it('does not throw `Class constructor Foo cannot be invoked without "new"` error', () => {
const foo = new Mod.Foo('lachlan')
expect(foo.hollaAt).to.eq('lachlan')
})
})
context('default export', () => {
it('does not throw `Class constructor Foo cannot be invoked without "new"` error', () => {
const baz = new Mod.default('lachlan')
expect(baz.hollaAt).to.eq('BAZ!')
})
})
})
it('works with react class component', () => {
mount(<Foo.BarClassComponent msg="Hello world" />)
cy.contains('Hello world').should('exist')
})
it('works with react function component', () => {
mount(<Foo.Foo msg="Hello world" />)
cy.contains('Hello world').should('exist')
})
it('inheritance via classes', () => {
expect(new Foo.Dog('bark').greet()).to.eq('bark')
})
/**
* Verifies a popular and complex library works properly.
* This library has some weird things going on with prototypes
* under the hood.
*/
it('works with react-query', () => {
mount(<App />)
})
it('works with array as default export', () => {
expect(Array.isArray(numbers)).to.be.true
expect(numbers).to.deep.eq([1, 2, 3])
})
it('works with array as named export', () => {
expect(Array.isArray(letters)).to.be.true
expect(letters).to.deep.eq(['a', 'b', 'c'])
})
// TODO: This errors when the lazy loaded router is a component
// This error has more details in the package README.
it.skip('works with react-router and lazy route', () => {
function App () {
return (
<BrowserRouter>
<Suspense fallback={<div />}>
<Routes>
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</BrowserRouter>
)
}
mount(<App />)
cy.get('h1').contains('About')
})
})
@@ -0,0 +1,35 @@
import React from 'react'
export const Foo: React.FC<{ msg: string }> = (props) => {
return (
<h1>{props.msg}</h1>
)
}
class Animal {
voice: string = 'Hello'
greet () {
return this.voice
}
}
export class Dog extends Animal {
constructor (voice: string) {
super()
this.voice = voice
}
}
export class BarClassComponent extends React.Component<{ msg: string }> {
/* eslint-disable @typescript-eslint/no-useless-constructor */
constructor (props) {
super(props)
}
render () {
return (
<h1>{this.props.msg}</h1>
)
}
}
@@ -0,0 +1,3 @@
export default function foo () {
console.log('foo')
}
@@ -0,0 +1,7 @@
export function unstubbable () {
return 'Hello from immutable'
}
export default function nospy () {
return 'You cannot spy on this'
}
@@ -0,0 +1,3 @@
export function add (a: number, b: number) {
return a + b
}
@@ -0,0 +1,21 @@
export class Foo {
private name: string
constructor (name: string) {
this.name = name
}
public get hollaAt () {
return this.name
}
}
export function Bar () {}
class Baz extends Foo {
public override get hollaAt () {
return 'BAZ!'
}
}
export default Baz
@@ -0,0 +1 @@
export default [1, 2, 3]
@@ -0,0 +1,5 @@
import React from 'react'
const About = () => <h1>About</h1>
export default About
@@ -0,0 +1,11 @@
export const export1 = 'export1'
export const export2 = 'export2'
// @ts-expect-error
window.sideEffect = 'Side Effect'
export default {
export1,
export2,
}
@@ -0,0 +1,13 @@
export const Mod = {
async fetcher () {
import('./mod_2').then((mod) => {
document.querySelector(`[data-cy-root]`)!.innerHTML = `<h1>${mod.greeting}</h1>`
})
},
run () {
window.setTimeout(() => {
this.fetcher()
}, 2000)
},
}
@@ -0,0 +1,3 @@
export const greeting = {
name: 'lachlan',
}
@@ -0,0 +1 @@
export const letters = ['a', 'b', 'c']
@@ -0,0 +1,48 @@
import {
useMutation,
useQueryClient,
QueryClient,
QueryClientProvider,
} from 'react-query'
import React from 'react'
// Create a client
const queryClient = new QueryClient()
export function App () {
return (
// Provide the client to your App
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
)
}
function Todos () {
// Access the client
const queryClient = useQueryClient()
async function postTodo (args: any) { }
// Mutations
const mutation = useMutation(postTodo, {
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries('todos')
},
})
return (
<div>
<button
onClick={() => {
mutation.mutate({
id: Date.now(),
title: 'Do Laundry',
})
}}
>
Add Todo
</button>
</div>
)
}
@@ -0,0 +1,3 @@
div {
background: red;
}
@@ -0,0 +1,47 @@
import * as Immutable from './fixtures/MyImmutableModule'
describe('ignoreList', () => {
it('ignoreList module is not proxified and cannot be stubbed', () => {
let called = false
try {
cy.stub(Immutable, 'unstubbable')
} catch (e) {
expect(e.message).to.eq('ES Modules cannot be stubbed')
called = true
}
expect(called).to.be.true
})
it('ignoreList module is not proxified and cannot be spied', () => {
let called = false
try {
cy.stub(Immutable, 'default')
} catch (e) {
expect(e.message).to.eq('ES Modules cannot be stubbed')
called = true
}
expect(called).to.be.true
})
it('correctly ignores dynamic imports', () => {
let called = false
async function run () {
const mod = await import('./fixtures/MyAsyncMod')
try {
cy.stub(mod, 'default')
mod.default()
} catch (e) {
called = true
expect(e.message).to.eq('ES Modules cannot be stubbed')
}
}
cy.wrap(run()).then(() => {
expect(called).to.be.true
})
})
})
@@ -0,0 +1,99 @@
/// <reference types="cypress" />
/* eslint-disable @typescript-eslint/no-duplicate-imports */
import defaultExport1 from './fixtures/kitchenSink'
import * as name1 from './fixtures/kitchenSink'
import { export1 } from './fixtures/kitchenSink'
import { export1 as alias1 } from './fixtures/kitchenSink'
import { default as alias } from './fixtures/kitchenSink'
import defaultExport2, { export2 } from './fixtures/kitchenSink'
import defaultExport3, * as name2 from './fixtures/kitchenSink'
import { export1 as e1, export2 as e2 } from './fixtures/kitchenSink'
import './fixtures/kitchenSink'
// Examples for all syntax
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
describe('supports every combination of import syntax in a single file', () => {
it('Import defaultExport1 from "./kitchenSink"', () => {
expect(defaultExport1).to.deep.eq({
export1: 'export1',
export2: 'export2',
default: {
export1: 'export1',
export2: 'export2',
},
})
})
it('Import * as name1 from "./kitchenSink"', () => {
expect(name1).to.deep.eq({
export1: 'export1',
export2: 'export2',
default: {
export1: 'export1',
export2: 'export2',
},
})
})
it('Import { export1 } from "./kitchenSink"', () => {
expect(export1).to.deep.eq(export1)
})
it('Import { export1 as alias1 } from "./kitchenSink"', () => {
expect(alias1).to.deep.eq(export1)
})
it('Import { default as alias } from "./kitchenSink"', () => {
expect(alias).to.deep.eq({
export1: 'export1',
export2: 'export2',
})
})
it('Import defaultExport2, { export2, } from "./kitchenSink"', () => {
expect(defaultExport2).to.deep.eq({
export1: 'export1',
export2: 'export2',
default: {
export1: 'export1',
export2: 'export2',
},
})
expect(export2).to.eq(export2)
})
it('Import defaultExport3, * as name2 from "./kitchenSink"', () => {
expect(defaultExport3).to.deep.eq({
export1: 'export1',
export2: 'export2',
default: {
export1: 'export1',
export2: 'export2',
},
})
})
it('Import X, * as Y from "module" syntax is not supported.', () => {
expect(name2).to.deep.eq({
export1: 'export1',
export2: 'export2',
default: {
export1: 'export1',
export2: 'export2',
},
})
})
it('Import { export1 as e1, export2 as e2 } from "./kitchenSink"', () => {
expect(e1).to.deep.eq(export1)
expect(e2).to.deep.eq(export2)
})
it('Import "./kitchenSink"', () => {
// @ts-expect-error
expect(window.sideEffect).to.eq('Side Effect')
})
})
@@ -0,0 +1,11 @@
/// <reference types="cypress" />
import * as M from './fixtures/add'
describe('spying ES modules', () => {
it('spies', () => {
cy.spy(M, 'add').as('add')
expect(M.add(2, 5)).to.eq(7)
cy.get('@add').should('have.been.calledWith', 2, 5)
})
})
@@ -0,0 +1,56 @@
/// <reference types="cypress" />
import React from 'react'
import * as M from './fixtures/add'
import { mount } from 'cypress/react'
import * as Foo from './fixtures/Foo'
import _ from 'lodash'
describe('stubbing ES modules', () => {
it('uses real implementation', () => {
expect(M.add(1, 2)).to.eq(3)
})
it('mocks', () => {
cy.stub(M, 'add').callsFake(function multiply (a: number, b: number) {
return a * b
})
// Plot twist - add actually does multiplication!
expect(M.add(1, 2)).to.eq(2)
})
it('uses real implementation again', () => {
expect(M.add(1, 2)).to.eq(3)
})
it('mocks with alias', () => {
const stub = cy.stub(M, 'add').as('add')
expect(M.add(1, 2)).to.be.undefined
cy.wrap(stub).should('have.been.called')
cy.get('@add').should('have.been.called')
})
it('works with react class component', () => {
mount(<Foo.BarClassComponent msg='Hello world' />)
cy.contains('Hello world').should('exist')
})
it('works with react function component', () => {
mount(<Foo.Foo msg='Hello world' />)
cy.contains('Hello world').should('exist')
})
it('stubs react component', () => {
cy.stub(Foo, 'Foo').callsFake(() => <h1>Stub Component</h1>)
mount(<Foo.Foo msg='Hello world' />)
cy.contains('Stub Component').should('exist')
cy.contains('Hello world').should('not.exist')
})
it('stubs lodash method from node_modules using static import', () => {
cy.stub(_, 'camelCase').callsFake(() => 'STUB')
expect(_.camelCase('aaaa')).to.eq('STUB')
})
})
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
</head>
<body>
<div data-cy-root></div>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
export * from './dist'
+1
View File
@@ -0,0 +1 @@
module.exports = require('./dist')
+45
View File
@@ -0,0 +1,45 @@
{
"name": "@cypress/vite-plugin-cypress-esm",
"version": "0.0.0-development",
"description": "Make ESM Modules mutable in the browser with Cypress and Vite",
"main": "index.js",
"scripts": {
"build": "tsc || echo 'built, with type errors'",
"build-prod": "tsc || echo 'built, with type errors'",
"check-ts": "tsc --noEmit",
"cypress:open": "node ../../scripts/cypress open --project . --component",
"test": "node ../../scripts/cypress run --project . --browser chrome --component",
"watch": "tsc -w",
"lint": "eslint --ext .js,.ts,.json, ."
},
"dependencies": {
"debug": "^4.3.4",
"picomatch": "2.3.0"
},
"devDependencies": {
"@types/picomatch": "2.3.0",
"@vitejs/plugin-react": "1.3.1",
"react": "16.8.6",
"react-dom": "16.8.6",
"react-query": "3.39.3",
"react-router": "6.10.0",
"react-router-dom": "6.10.0",
"vite": "4.1.4"
},
"files": [
"dist",
"client",
"index.d.ts"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/cypress-io/cypress.git"
},
"homepage": "https://github.com/cypress-io/cypress/tree/develop/npm/vite-plugin-cypress-esm#readme",
"bugs": "https://github.com/cypress-io/cypress/issues/new?labels=npm:%20@cypress/vite-plugin-cypress-esm",
"module": "dist/index.js",
"publishConfig": {
"access": "public"
}
}
+184
View File
@@ -0,0 +1,184 @@
import debugFn from 'debug'
import picomatch from 'picomatch'
import type { Plugin } from 'vite'
import fs from 'fs'
import path from 'path'
const debug = debugFn('cypress:vite-plugin-mock-esm')
const MODULE_IMPORTER_IDENTIFIER = '__cypressModule'
const MODULE_DYNAMIC_IMPORTER_IDENTIFIER = '__cypressDynamicModule'
const MODULE_CACHE_FILEPATH = path.resolve(
__dirname,
'../client/moduleCache.js',
)
interface CypressEsmOptions {
ignoreList?: string[]
}
export const CypressEsm = (options?: CypressEsmOptions): Plugin => {
const ignoreList = options?.ignoreList ?? []
const importOnIgnoreList = (importTarget: string) => {
for (const ignore of ignoreList) {
if (picomatch.matchBase(importTarget, ignore)) {
return true
}
}
}
/**
* Remap static import calls to use the Cypress module cache
*
* ```
* import DefaultExport, { NamedExport, Other as Alias } from 'module'
* ```
*
* becomes
*
* ```
* import * as _cypress_module_1 from 'module';
* const DefaultExport = __cypressModule(_cypress_module_1);
* const { NamedExport, Other: Alias } = __cypressModule(_cypress_module_1);
* ```
*/
const mapImportsToCache = (moduleId: string, code: string) => {
debug(`Remapping imports for module ${moduleId}`)
let counter = 0
const moduleIdentifier = moduleId.replace(/[^a-zA-Z\d]/g, '_')
const importRegex = /import (.+?) from (.*)/g
return code.replace(
importRegex,
(match, importVars: string, importTarget: string) => {
debug(`Mapping import ${counter + 1} in module ${moduleId}`)
let replacement = `import * as cypress_${moduleIdentifier}_${++counter} from ${importTarget}`
if (importOnIgnoreList(importTarget)) {
return match
}
if (!replacement.endsWith(';')) {
replacement += ';'
}
// Split the import declaration into named vs non-named segments
// e.g.: import TheDefault, { Named }, * as Everything from 'module'
// ======> ['TheDefault', '{ Named }', 'Everything']
importVars
.split(/(?:(?<=})\s*,\s*)|(?:\s*,\s*(?={))/g)
.forEach((importVar) => {
const declarations = []
// If we're handling a destructure assignment then there aren't any special cases, can map through
// as a single assignment operation
if (importVar.includes('{')) {
declarations.push(importVar)
} else {
// Outside of a destructure we need to split each comma block as a separate assignment...
declarations.push(
...importVar
.split(',')
// ...because we need to account for potential `* as foo` syntax
.map((decl) => decl.replace(/\*\s+as\s+/g, '').trim())
.filter((decl) => !!decl),
)
}
let destructuredImports = declarations
.map((decl) => {
// support `import { foo as bar } from 'module'` syntax, converting to `const { foo: bar } ...`
decl = decl.replace(/(?<!\*) as /g, ': ')
return `const ${decl} = ${MODULE_IMPORTER_IDENTIFIER}('${moduleId}', cypress_${moduleIdentifier}_${counter}, ${debug.enabled});`
})
.join('')
replacement += destructuredImports
})
return match
.replace(importRegex, replacement)
.replace(/[\n\r]/g, '')
.replace(/const \* as/g, 'const')
},
)
}
/**
* Transform dynamic imports to use a runtime that turns the ES modules into proxies by injecting
* a `__cypressDynamicModule` runtime.
*
* `__cypressDynamicModule` returns a Promise that resolves to the original import which has been
* "proxified" to side step the fact ES Modules are immutable and sealed.
*
* Examples - https://regex101.com/r/Ic1OHA/1
*
* Given:
* const m = import("./mod_1")
* const m = await import("lodash")
* import("./mod_2").then(mod => mod)
*
* Returns:
* const m = __cypressDynamicModule(import("./mod_1"))
* const m = await __cypressDynamicModule(import("lodash"))
* __cypressDynamicModule(import("./mod_2")).then(mod => mod)
*/
const mapDynamicImportsToCache = (id: string, code: string) => {
const RE = /(import\((.+?)\))/g
return code.replace(
RE,
(match, importVars: string, importTarget: string) => {
if (importOnIgnoreList(importTarget)) {
return match
}
return match.replace(
RE,
`${MODULE_DYNAMIC_IMPORTER_IDENTIFIER}('${id}', $1, ${debug.enabled})`,
)
},
)
}
return {
name: 'cypress:mocks',
enforce: 'post',
transform (code, id, options) {
// Process all files to remap imports
// TODO: Restrict to JS files only? Any potential for .mjs etc?
let transformedCode = mapImportsToCache(id, code)
transformedCode = mapDynamicImportsToCache(id, transformedCode)
return {
code: transformedCode,
}
},
async transformIndexHtml (html) {
// Process index.html file to remap imports
let transformedHtml = mapImportsToCache('index', html)
transformedHtml = mapDynamicImportsToCache('index', transformedHtml)
return {
html: transformedHtml,
tags: [
// Pull in Cypress module cache script
{
tag: 'script',
attrs: {
type: 'module',
},
// eslint-disable-next-line no-restricted-syntax
children: fs.readFileSync(MODULE_CACHE_FILEPATH, 'utf-8'),
},
],
}
},
}
}
+53
View File
@@ -0,0 +1,53 @@
{
"compilerOptions": {
/* Basic Options */
"resolveJsonModule": true,
"target": "ES2017" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
"lib": [
"es2015",
"dom"
] /* Specify library files to be included in the compilation: */,
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true /* Generates corresponding '.d.ts' file. */,
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "dist" /* Redirect output structure to the directory. */,
// "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
"importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
// "noImplicitAny": true,
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
"types": ["cypress"] /* Type declaration files to be included in compilation. */,
"allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
/* Source Map Options */
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"esModuleInterop": true,
/** Allows us to strip internal types sourced from webpack */
"stripInternal": true
},
"include": ["src"],
"exclude": ["node_modules", "*.js"]
}
+2 -2
View File
@@ -18,7 +18,7 @@
},
"devDependencies": {
"@cypress/mount-utils": "0.0.0-development",
"@vitejs/plugin-vue": "2.3.1",
"@vitejs/plugin-vue": "4.2.0",
"@vue/compiler-sfc": "3.2.31",
"@vue/test-utils": "2.0.2",
"axios": "0.21.2",
@@ -27,7 +27,7 @@
"globby": "^11.0.1",
"tailwindcss": "1.1.4",
"typescript": "^4.7.4",
"vite": "4.1.4",
"vite": "4.3.2",
"vue": "3.2.31",
"vue-i18n": "9.0.0-rc.6",
"vue-router": "^4.0.0",
+7
View File
@@ -1,3 +1,10 @@
# [@cypress/webpack-dev-server-v3.4.1](https://github.com/cypress-io/cypress/compare/@cypress/webpack-dev-server-v3.4.0...@cypress/webpack-dev-server-v3.4.1) (2023-04-07)
### Bug Fixes
* correctly pass resolvedBaseUrl for Next.js 13.2.0+ ([#26399](https://github.com/cypress-io/cypress/issues/26399)) ([e8390f4](https://github.com/cypress-io/cypress/commit/e8390f46cd852417f2c0b07a9c73eeaf7437e823))
# [@cypress/webpack-dev-server-v3.4.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-dev-server-v3.3.1...@cypress/webpack-dev-server-v3.4.0) (2023-03-20)
@@ -2,12 +2,12 @@
/// <reference path="../support/e2e.ts" />
import type { ProjectFixtureDir } from '@tooling/system-tests/lib/fixtureDirs'
const WEBPACK_REACT: ProjectFixtureDir[] = ['angular-13', 'angular-14', 'angular-15']
const WEBPACK_ANGULAR: ProjectFixtureDir[] = ['angular-13', 'angular-14', 'angular-15', 'angular-16']
// Add to this list to focus on a particular permutation
const ONLY_PROJECTS: ProjectFixtureDir[] = []
for (const project of WEBPACK_REACT) {
for (const project of WEBPACK_ANGULAR) {
if (ONLY_PROJECTS.length && !ONLY_PROJECTS.includes(project)) {
continue
}
@@ -260,28 +260,33 @@ async function getAngularCliWebpackConfig (devServerConfig: AngularWebpackDevSer
buildOptions,
context,
(wco: any) => {
const stylesConfig = getStylesConfig(wco)
// We modify resolve-url-loader and set `root` to be `projectRoot` + `sourceRoot` to ensure
// imports in scss, sass, etc are correctly resolved.
// https://github.com/cypress-io/cypress/issues/24272
stylesConfig.module.rules.forEach((rule: RuleSetRule) => {
rule.rules?.forEach((ruleSet) => {
if (!Array.isArray(ruleSet.use)) {
return
}
ruleSet.use.map((loader) => {
if (typeof loader !== 'object' || typeof loader.options !== 'object' || !loader.loader?.includes('resolve-url-loader')) {
// Starting in Angular 16, the `getStylesConfig` function returns a `Promise`.
// We wrap it with `Promise.resolve` so we support older version of Angular
// returning a non-Promise result.
const stylesConfig = Promise.resolve(getStylesConfig(wco)).then((config) => {
// We modify resolve-url-loader and set `root` to be `projectRoot` + `sourceRoot` to ensure
// imports in scss, sass, etc are correctly resolved.
// https://github.com/cypress-io/cypress/issues/24272
config.module.rules.forEach((rule: RuleSetRule) => {
rule.rules?.forEach((ruleSet) => {
if (!Array.isArray(ruleSet.use)) {
return
}
const root = projectConfig.buildOptions.workspaceRoot || path.join(devServerConfig.cypressConfig.projectRoot, projectConfig.sourceRoot)
ruleSet.use.map((loader) => {
if (typeof loader !== 'object' || typeof loader.options !== 'object' || !loader.loader?.includes('resolve-url-loader')) {
return
}
debug('Adding root %s to resolve-url-loader options', root)
loader.options.root = root
const root = projectConfig.buildOptions.workspaceRoot || path.join(devServerConfig.cypressConfig.projectRoot, projectConfig.sourceRoot)
debug('Adding root %s to resolve-url-loader options', root)
loader.options.root = root
})
})
})
return config
})
return [getCommonConfig(wco), stylesConfig]
@@ -29,7 +29,12 @@ export async function nextHandler (devServerConfig: WebpackDevServerConfig): Pro
*/
function getNextJsPackages (devServerConfig: WebpackDevServerConfig) {
const resolvePaths = { paths: [devServerConfig.cypressConfig.projectRoot] }
const packages = {} as { loadConfig: Function, getNextJsBaseWebpackConfig: Function, nextLoadJsConfig: Function }
const packages = {} as {
loadConfig: (phase: 'development', dir: string) => Promise<any>
getNextJsBaseWebpackConfig: Function
nextLoadJsConfig: Function
getSupportedBrowsers: (dir: string, isDevelopment: boolean, nextJsConfig: any) => Promise<string[] | undefined>
}
try {
const loadConfigPath = require.resolve('next/dist/server/config', resolvePaths)
@@ -55,6 +60,15 @@ function getNextJsPackages (devServerConfig: WebpackDevServerConfig) {
throw new Error(`Failed to load "next/dist/build/load-jsconfig" with error: ${ e.message ?? e}`)
}
// Does not exist prior to Next 13.
try {
const getUtilsPath = require.resolve('next/dist/build/utils', resolvePaths)
packages.getSupportedBrowsers = require(getUtilsPath).getSupportedBrowsers ?? (() => Promise.resolve([]))
} catch (e: any) {
throw new Error(`Failed to load "next/dist/build/utils" with error: ${ e.message ?? e}`)
}
return packages
}
@@ -173,12 +187,13 @@ function getNextJsPackages (devServerConfig: WebpackDevServerConfig) {
]
*/
async function loadWebpackConfig (devServerConfig: WebpackDevServerConfig): Promise<Configuration> {
const { loadConfig, getNextJsBaseWebpackConfig, nextLoadJsConfig } = getNextJsPackages(devServerConfig)
const { loadConfig, getNextJsBaseWebpackConfig, nextLoadJsConfig, getSupportedBrowsers } = getNextJsPackages(devServerConfig)
const nextConfig = await loadConfig('development', devServerConfig.cypressConfig.projectRoot)
const runWebpackSpan = getRunWebpackSpan(devServerConfig)
const reactVersion = getReactVersion(devServerConfig.cypressConfig.projectRoot)
const jsConfigResult = await nextLoadJsConfig?.(devServerConfig.cypressConfig.projectRoot, nextConfig)
const supportedBrowsers = await getSupportedBrowsers(devServerConfig.cypressConfig.projectRoot, true, nextConfig)
const webpackConfig = await getNextJsBaseWebpackConfig(
devServerConfig.cypressConfig.projectRoot,
@@ -196,8 +211,12 @@ async function loadWebpackConfig (devServerConfig: WebpackDevServerConfig): Prom
compilerType: 'client',
// Required for Next.js > 13
hasReactRoot: reactVersion === 18,
// Required for Next.js > 13.2.1 to respect TS/JS config
// Required for Next.js > 13.2.0 to respect TS/JS config
jsConfig: jsConfigResult.jsConfig,
// Required for Next.js > 13.2.0 to respect tsconfig.compilerOptions.baseUrl
resolvedBaseUrl: jsConfigResult.resolvedBaseUrl,
// Added in Next.js 13, passed via `...info`: https://github.com/vercel/next.js/pull/45637/files
supportedBrowsers,
},
)
+2
View File
@@ -1,3 +1,5 @@
# [@cypress/webpack-preprocessor-v5.17.1](https://github.com/cypress-io/cypress/compare/@cypress/webpack-preprocessor-v5.17.0...@cypress/webpack-preprocessor-v5.17.1) (2023-05-01)
# [@cypress/webpack-preprocessor-v5.17.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-preprocessor-v5.16.3...@cypress/webpack-preprocessor-v5.17.0) (2023-02-15)
@@ -1,6 +1,6 @@
exports['webpack preprocessor - e2e correctly preprocesses the file 1'] = `
it("is a test",(function(){expect(1).to.equal(1),expect(2).to.equal(2),expect(Math.min.apply(Math,[3,4])).to.equal(3)}));
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZV9zcGVjX291dHB1dC5qcyIsIm1hcHBpbmdzIjoiQUFBQUEsR0FBRyxhQUFhLFdBR2RDLE9BRmdCLEdBRU5DLEdBQUdDLE1BQU0sR0FDbkJGLE9BSG1CLEdBR1RDLEdBQUdDLE1BQU0sR0FDbkJGLE9BQU9HLEtBQUtDLElBQUcsTUFBUkQsS0FBWSxDQUFDLEVBQUcsS0FBS0YsR0FBR0MsTUFBTSxFQUN2QyIsInNvdXJjZXMiOlsid2VicGFjazovL0BjeXByZXNzL3dlYnBhY2stcHJlcHJvY2Vzc29yLy4vdGVzdC9fdGVzdC1vdXRwdXQvZXhhbXBsZV9zcGVjLmpzIl0sInNvdXJjZXNDb250ZW50IjpbIml0KCdpcyBhIHRlc3QnLCAoKSA9PiB7XG4gIGNvbnN0IFthLCBiXSA9IFsxLCAyXVxuXG4gIGV4cGVjdChhKS50by5lcXVhbCgxKVxuICBleHBlY3QoYikudG8uZXF1YWwoMilcbiAgZXhwZWN0KE1hdGgubWluKC4uLlszLCA0XSkpLnRvLmVxdWFsKDMpXG59KVxuIl0sIm5hbWVzIjpbIml0IiwiZXhwZWN0IiwidG8iLCJlcXVhbCIsIk1hdGgiLCJtaW4iXSwic291cmNlUm9vdCI6IiJ9
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXhhbXBsZV9zcGVjX291dHB1dC5qcyIsIm1hcHBpbmdzIjoiQUFBQUEsR0FBRyxhQUFhLFdBR2RDLE9BRmdCLEdBRU5DLEdBQUdDLE1BQU0sR0FDbkJGLE9BSG1CLEdBR1RDLEdBQUdDLE1BQU0sR0FDbkJGLE9BQU9HLEtBQUtDLElBQUdDLE1BQVJGLEtBQVksQ0FBQyxFQUFHLEtBQUtGLEdBQUdDLE1BQU0sRUFDdkMiLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly9AY3lwcmVzcy93ZWJwYWNrLXByZXByb2Nlc3Nvci8uL3Rlc3QvX3Rlc3Qtb3V0cHV0L2V4YW1wbGVfc3BlYy5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJpdCgnaXMgYSB0ZXN0JywgKCkgPT4ge1xuICBjb25zdCBbYSwgYl0gPSBbMSwgMl1cblxuICBleHBlY3QoYSkudG8uZXF1YWwoMSlcbiAgZXhwZWN0KGIpLnRvLmVxdWFsKDIpXG4gIGV4cGVjdChNYXRoLm1pbiguLi5bMywgNF0pKS50by5lcXVhbCgzKVxufSlcbiJdLCJuYW1lcyI6WyJpdCIsImV4cGVjdCIsInRvIiwiZXF1YWwiLCJNYXRoIiwibWluIiwiYXBwbHkiXSwic291cmNlUm9vdCI6IiJ9
`
exports['webpack preprocessor - e2e has less verbose syntax error 1'] = `
+1 -1
View File
@@ -37,7 +37,7 @@
"common-tags": "^1.8.2",
"cypress": "0.0.0-development",
"dependency-check": "2.9.1",
"deps-ok": "1.2.1",
"deps-ok": "1.4.1",
"fast-glob": "3.1.1",
"find-webpack": "1.5.0",
"fs-extra": "^10.1.0",
+4 -5
View File
@@ -1,6 +1,6 @@
{
"name": "cypress",
"version": "12.9.0",
"version": "12.11.0",
"description": "Cypress is a next generation front end testing tool built for the modern web",
"private": true,
"scripts": {
@@ -49,7 +49,7 @@
"stop-only": "npx stop-only --skip .cy,.publish,.projects,node_modules,dist,dist-test,fixtures,lib,bower_components,src,__snapshots__ --exclude cypress-tests.ts,*only.cy.js",
"stop-only-all": "yarn stop-only --folder packages",
"pretest": "yarn ensure-deps",
"test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,data-context,electron,errors,extension,https-proxy,launcher,net-stubbing,network,packherd-require,proxy,rewriter,scaffold-config,socket,v8-snapshot-require}'\" --scope \"'@tooling/{electron-mksnapshot,v8-snapshot}'\"",
"test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,data-context,electron,errors,extension,https-proxy,launcher,net-stubbing,network,packherd-require,proxy,rewriter,scaffold-config,socket,v8-snapshot-require,telemetry}'\" --scope \"'@tooling/{electron-mksnapshot,v8-snapshot}'\"",
"test-debug": "lerna exec yarn test-debug --ignore \"'@packages/{driver,root,static,web-config}'\"",
"pretest-e2e": "yarn ensure-deps",
"test-integration": "lerna exec yarn test-integration --ignore \"'@packages/{driver,root,static,web-config}'\"",
@@ -103,7 +103,7 @@
"@types/glob": "7.1.1",
"@types/gulp": "^4.0.9",
"@types/lodash": "^4.14.168",
"@types/markdown-it": "0.0.9",
"@types/markdown-it": "12.2.3",
"@types/mini-css-extract-plugin": "1.2.3",
"@types/mocha": "8.0.3",
"@types/node": "14.14.31",
@@ -188,7 +188,6 @@
"patch-package": "6.4.7",
"playwright-webkit": "1.24.2",
"pluralize": "8.0.0",
"postinstall-postinstall": "2.0.0",
"print-arch": "1.0.0",
"proxyquire": "2.1.3",
"rimraf": "3.0.2",
@@ -276,4 +275,4 @@
"sharp": "0.29.3",
"vue-template-compiler": "2.6.12"
}
}
}
+3 -1
View File
@@ -1,2 +1,4 @@
cypress/videos/*
cypress/screenshots/*
cypress/screenshots/*
components.d.ts
@@ -18,8 +18,6 @@ import { registerMountFn } from '@packages/frontend-shared/cypress/support/commo
// Import commands.js using ES2015 syntax:
import 'virtual:windi.css'
import '../../../src/main.scss'
import '@iconify/iconify'
import { createRouter } from '../../../src/router/router'
import { createPinia } from '../../../src/store'
+2 -2
View File
@@ -64,7 +64,7 @@ describe('App - Debug Page', () => {
cy.findByTestId('header-top').contains('update projectId')
cy.findByTestId('debug-header-dashboard-link')
.contains('View in Cypress Cloud')
.should('have.attr', 'href', 'https://cloud.cypress.io/projects/7p5uce/runs/2')
.should('have.attr', 'href', 'https://cloud.cypress.io/projects/7p5uce/runs/2?utm_medium=Debug+Tab&utm_campaign=View+in+Cypress+Cloud&utm_source=Binary%3A+App')
cy.findByTestId('debug-runNumber-PASSED').contains('#2')
cy.findByTestId('debug-commitsAhead').contains('You are 1 commit ahead')
@@ -133,7 +133,7 @@ describe('App - Debug Page', () => {
cy.findByTestId('header-top').contains('chore: testing cypress')
cy.findByTestId('debug-header-dashboard-link')
.contains('View in Cypress Cloud')
.should('have.attr', 'href', 'https://cloud.cypress.io/projects/vgqrwp/runs/136')
.should('have.attr', 'href', 'https://cloud.cypress.io/projects/vgqrwp/runs/136?utm_medium=Debug+Tab&utm_campaign=View+in+Cypress+Cloud&utm_source=Binary%3A+App')
cy.findByLabelText('Relevant run had 1 test failure').should('be.visible').contains('1')
+7 -7
View File
@@ -100,7 +100,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
cy.contains('a', 'OVERLIMIT').click()
cy.withCtx((ctx) => {
expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.eq('http://dummy.cypress.io/runs/4')
expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.contain('http://dummy.cypress.io/runs/4')
})
})
})
@@ -323,7 +323,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
moveToRunsPage()
cy.findByText(defaultMessages.runs.connect.buttonProject).click()
cy.contains('button', defaultMessages.runs.connect.modal.selectProject.createProject).click()
cy.findByText(defaultMessages.runs.connectSuccessAlert.title).should('be.visible')
cy.findByText(defaultMessages.runs.connectSuccessAlert.title, { timeout: 10000 }).should('be.visible')
cy.withCtx(async (ctx) => {
const config = await ctx.project.getConfig()
@@ -660,22 +660,22 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
cy.visitApp()
moveToRunsPage()
cy.get('[href="http://dummy.cypress.io/runs/0"]').first().within(() => {
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.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.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('[href^="http://dummy.cypress.io/runs/0"]').first().as('firstRun')
cy.get('@firstRun').within(() => {
cy.get('[data-cy="run-card-author"]').contains('John Appleseed')
@@ -699,7 +699,7 @@ describe('App: Runs', { viewportWidth: 1200 }, () => {
cy.get('[data-cy^="runCard-"]').first().click()
cy.withCtx((ctx) => {
expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.eq('http://dummy.cypress.io/runs/0')
expect((ctx.actions.electron.openExternal as SinonStub).lastCall.lastArg).to.contain('http://dummy.cypress.io/runs/0')
})
})
+1 -1
View File
@@ -111,7 +111,7 @@ describe('App Top Nav Workflows', () => {
expect(ctx.actions.browser.setActiveBrowserById).to.have.been.calledWith(browserId)
expect(genId).to.eql('edge-chromium-stable')
expect(ctx.actions.project.launchProject).to.have.been.calledWith(
ctx.coreData.currentTestingType, { shouldLaunchNewTab: false }, undefined,
ctx.coreData.currentTestingType, { shouldLaunchNewTab: false }, '',
)
})
})
@@ -5,6 +5,7 @@
"cloudProject": {
"__typename": "CloudProject",
"id": "cloud-project-test-id",
"cloudProjectUrl": "https://cloud.cypress.io/projects/vgqrwp",
"runByNumber": {
"id": "Q2xvdWRSdW46TUdWZXhvQkRPNg==",
"runNumber": 136,
@@ -5,6 +5,7 @@
"cloudProject": {
"__typename": "CloudProject",
"id": "Q2xvdWRQcm9qZWN0OjdwNXVjZQ==",
"cloudProjectUrl": "https://cloud.cypress.io/projects/7p5uce",
"runByNumber": {
"id": "Q2xvdWRSdW46bkdudmx5d3BHWg==",
"runNumber": 2,
+5 -5
View File
@@ -13,9 +13,9 @@ export {}
* To work around this, we build the driver, eventManager
* and some other dependencies using webpack, and consumed the dist'd
* source code.
*
*
* This is attached to `window` under the `UnifiedRunner` namespace.
*
*
* For now, just declare the types that we need to give us type safety where possible.
* Eventually, we should decouple the event manager and import it directly.
*/
@@ -37,7 +37,7 @@ declare global {
* We get a reference to the copy of React (and React DOM)
* that is used in the Reporter and Driver, which are bundled with
* webpack.
*
*
* Unfortunately, attempting to have React in a project
* using Vue causes mad conflicts because React'S JSX type
* is ambient, so we cannot actually type it.
@@ -54,7 +54,7 @@ declare global {
* Any React components or general code needed from
* runner, reporter or driver are also bundled with
* webpack and made available via the window.UnifedRunner namespace.
*
*
* We cannot import the correct types, because this causes the linter and type
* checker to run on runner and reporter, and it blows up.
*/
@@ -64,4 +64,4 @@ declare global {
}
}
}
}
}
+13 -13
View File
@@ -20,24 +20,25 @@
},
"dependencies": {},
"devDependencies": {
"@cypress-design/vue-icon": "0.18.1",
"@cypress-design/vue-statusicon": "0.2.4",
"@cypress-design/vue-icon": "0.20.0",
"@cypress-design/vue-statusicon": "0.3.0",
"@graphql-typed-document-node/core": "^3.1.0",
"@headlessui/vue": "1.4.0",
"@iconify/iconify": "2.1.2",
"@iconify/json": "1.1.368",
"@iconify/vue": "3.0.0-beta.1",
"@intlify/vite-plugin-vue-i18n": "2.4.0",
"@intlify/unplugin-vue-i18n": "0.10.0",
"@packages/frontend-shared": "0.0.0-development",
"@packages/telemetry": "0.0.0-development",
"@percy/cypress": "^3.1.0",
"@popperjs/core": "2.11.6",
"@testing-library/cypress": "9.0.0",
"@types/faker": "5.5.8",
"@urql/core": "2.4.4",
"@urql/vue": "0.6.2",
"@vitejs/plugin-legacy": "^2.1.0",
"@vitejs/plugin-vue": "2.2.4",
"@vitejs/plugin-vue-jsx": "1.3.8",
"@vitejs/plugin-legacy": "4.0.3",
"@vitejs/plugin-vue": "4.2.0",
"@vitejs/plugin-vue-jsx": "3.0.1",
"@vueuse/core": "7.2.2",
"ansi-to-html": "0.6.14",
"bluebird": "3.5.3",
@@ -63,13 +64,12 @@
"rollup-plugin-copy": "3.4.0",
"rollup-plugin-polyfill-node": "^0.7.0",
"unplugin-icons": "0.13.2",
"unplugin-vue-components": "^0.15.2",
"vite": "4.1.4",
"vite-plugin-components": "0.11.3",
"vite-plugin-pages": "0.18.1",
"vite-plugin-vue-layouts": "0.6.0",
"vite-svg-loader": "3.1.2",
"vue": "3.2.31",
"unplugin-vue-components": "^0.24.1",
"vite": "4.3.2",
"vite-plugin-pages": "0.29.0",
"vite-plugin-vue-layouts": "0.8.0",
"vite-svg-loader": "4.0.0",
"vue": "3.2.47",
"vue-i18n": "9.2.0-beta.7",
"vue-router": "4",
"vue-tsc": "^0.3.0",
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
-14
View File
@@ -19,17 +19,3 @@ import { isRunMode } from '@packages/frontend-shared/src/utils/isRunMode'
import LoginConnectModals from '@cy/gql-components/LoginConnectModals.vue'
import CloudViewerAndProject from '@packages/frontend-shared/src/gql-components/CloudViewerAndProject.vue'
</script>
<style lang="scss">
html,
body,
#app {
@apply bg-white h-full;
}
@font-face {
font-family: "Fira Code";
src: local("Fira Code"),
url('../../frontend-shared/src/assets/fonts/FiraCode-VariableFont_wght.ttf') format("truetype");
}
</style>
+8 -8
View File
@@ -28,23 +28,23 @@
<template>
<div>
<div class="rounded border-1 h-40px w-full inline-flex items-center hocus-default focus-within-default truncate">
<div class="rounded border h-[40px] w-full inline-flex items-center hocus-default focus-within-default truncate">
<FileMatchButton
:expanded="expanded"
@click="toggleExpanded()"
>
<span v-if="!expanded">{{ localExtensionPattern }}</span>
</FileMatchButton>
<div class="flex-grow min-w-min inline-flex items-center group">
<div class="grow min-w-min inline-flex items-center group">
<i-cy-magnifying-glass_x16
v-if="!expanded"
class="mr-8px ml-12px inline-block icon-light-gray-50 icon-dark-gray-500 group-focus-within:icon-light-indigo-50 group-focus-within:icon-dark-indigo-400"
class="mr-[8px] ml-[12px] inline-block icon-light-gray-50 icon-dark-gray-500 group-focus-within:icon-light-indigo-50 group-focus-within:icon-dark-indigo-400"
/>
<FileMatchInput
v-if="expanded"
v-model="localExtensionPattern"
class="ml-12px"
class="ml-[12px]"
:placeholder="t('components.fileSearch.byExtensionInput')"
/>
<FileMatchInput
@@ -56,7 +56,7 @@
</div>
<slot name="matches">
<FileMatchIndicator
class="mr-8px truncate"
class="mr-[8px] truncate"
data-cy="file-match-indicator"
>
{{ indicatorText }}
@@ -65,11 +65,11 @@
</div>
<div
class="rounded border-1 h-40px mt-8px w-full inline-flex items-center hocus-default focus-within-default"
class="rounded border h-[40px] mt-[8px] w-full inline-flex items-center hocus-default focus-within-default"
:class="{ 'hidden' : !expanded }"
>
<div class="flex-grow inline-flex items-center group">
<i-cy-magnifying-glass_x16 class="mr-8px ml-12px inline-block icon-light-gray-50 icon-dark-gray-500 group-focus-within:icon-light-indigo-50 group-focus-within:icon-dark-indigo-400" />
<div class="grow inline-flex items-center group">
<i-cy-magnifying-glass_x16 class="mr-[8px] ml-[12px] inline-block icon-light-gray-50 icon-dark-gray-500 group-focus-within:icon-light-indigo-50 group-focus-within:icon-dark-indigo-400" />
<FileMatchInput
v-model="localPattern"
:placeholder="t('components.fileSearch.byFilenameInput')"
@@ -1,10 +1,10 @@
<template>
<button
data-cy="file-match-button"
class="inline-flex items-center h-full text-gray-700 transition duration-150 rounded-l outline-none group bg-gray-50 border-r-gray-100 border-r-1 hocus:bg-indigo-50 hocus:border-r-indigo-300 hocus:text-indigo-500 px-12px"
class="inline-flex items-center h-full text-gray-700 transition duration-150 rounded-l outline-none group bg-gray-50 border-r-gray-100 border-r hocus:bg-indigo-50 hocus:border-r-indigo-300 hocus:text-indigo-500 px-[12px]"
>
<i-cy-chevron-right-small_x16
class="transition duration-150 transform min-w-16px min-h-16px icon-dark-gray-400 group-hocus:icon-dark-indigo-400"
class="transition duration-150 transform min-w-[16px] min-h-[16px] icon-dark-gray-400 group-hocus:icon-dark-indigo-400"
:class="{
'rotate-90': expanded
}"
@@ -4,7 +4,7 @@
data-cy="file-match-indicator"
>
<span
class="rounded font-medium h-24px text-center px-8px truncate select-none"
class="rounded font-medium h-[24px] text-center px-[8px] truncate select-none"
:class="color"
>
<slot /></span>
@@ -1,7 +1,7 @@
<template>
<input
v-model="localModelValue"
class="flex-grow p-0 text-gray-700 placeholder-gray-400 border-transparent outline-none placeholder-shown:overflow-ellipsis placeholder-shown:truncate hocus:border-transparent mr-8px"
class="grow p-0 text-gray-700 placeholder-gray-400 border-transparent outline-none placeholder-shown:overflow-ellipsis placeholder-shown:truncate hocus:border-transparent mr-[8px]"
type="search"
autocomplete="off"
>
@@ -9,7 +9,7 @@
@update:model-value="emits('close')"
>
<div
class="w-full p-24px sm:min-w-640px"
class="w-full p-[24px] sm:min-w-[640px]"
>
<SpecPatterns
:gql="props.gql"
@@ -17,7 +17,7 @@
/>
</div>
<StandardModalFooter
class="flex gap-16px items-center"
class="flex gap-[16px] items-center"
>
<OpenConfigFileInIDE
v-slot="{onClick}"
@@ -4,7 +4,7 @@ import { SpecPatternsFragmentDoc } from '../generated/graphql-test'
describe('<SpecPatterns />', () => {
it('renders spec patterns', () => {
cy.mountFragment(SpecPatternsFragmentDoc, {
render: (gql) => <div class="p-16px"><SpecPatterns gql={gql} /></div>,
render: (gql) => <div class="p-[16px]"><SpecPatterns gql={gql} /></div>,
})
cy.get('[data-cy="spec-pattern"]').contains('cypress/e2e/**/*.cy.{js,jsx,ts,tsx}')
@@ -23,7 +23,7 @@ describe('<SpecPatterns />', () => {
res.currentTestingType = 'component'
res.specs = res.specs.slice(0, 50) || []
},
render: (gql) => <div class="p-16px"><SpecPatterns gql={gql} /></div>,
render: (gql) => <div class="p-[16px]"><SpecPatterns gql={gql} /></div>,
})
cy.get('[data-cy="spec-pattern"]').contains('**/*.cy.{js,jsx,ts,tsx}')
@@ -40,7 +40,7 @@ describe('<SpecPatterns />', () => {
res.currentTestingType = 'component'
res.specs = res.specs.slice(0, 50) || []
},
render: (gql) => <div class="p-16px"><SpecPatterns gql={gql} variant='info' /></div>,
render: (gql) => <div class="p-[16px]"><SpecPatterns gql={gql} variant='info' /></div>,
})
cy.get('[data-cy="spec-pattern"]').contains('**/*.cy.{js,jsx,ts,tsx}')
@@ -57,7 +57,7 @@ describe('<SpecPatterns />', () => {
res.currentTestingType = 'component'
res.specs = []
},
render: (gql) => <div class="p-16px"><SpecPatterns gql={gql}/></div>,
render: (gql) => <div class="p-[16px]"><SpecPatterns gql={gql}/></div>,
})
cy.get('[data-cy="file-match-indicator"]').contains('No matches')
@@ -73,7 +73,7 @@ describe('<SpecPatterns />', () => {
res.currentTestingType = 'component'
res.specs = res.specs.slice(0, 1) || []
},
render: (gql) => <div class="p-16px"><SpecPatterns gql={gql}/></div>,
render: (gql) => <div class="p-[16px]"><SpecPatterns gql={gql}/></div>,
})
cy.get('[data-cy="file-match-indicator"]').contains('1 match')
+5 -5
View File
@@ -1,6 +1,6 @@
<template>
<div class="rounded border-gray-100 border-1px w-full">
<div class="flex p-16px items-center justify-between">
<div class="rounded border-gray-100 border-[1px] w-full">
<div class="flex p-[16px] items-center justify-between">
<FileMatchIndicator :variant="props.variant">
<span v-if="props.variant === 'info'">specPattern</span>
<span v-else>{{ t('components.specPattern.matches', props.gql.specs.length) }}</span>
@@ -10,7 +10,7 @@
:gql="props.gql"
>
<button
class="flex outline-transparent text-indigo-500 gap-8px items-center group"
class="flex outline-transparent text-indigo-500 gap-[8px] items-center group"
@click="onClick"
>
<i-cy-document-text_x16 class="icon-light-gray-100 icon-dark-gray-500" />
@@ -19,11 +19,11 @@
</OpenConfigFileInIDE>
</div>
<div class="divide-gray-200 divide-y-1 bg-gray-50 px-16px">
<div class="divide-gray-200 divide-y bg-gray-50 px-[16px]">
<code
v-for="pattern in specPatterns"
:key="pattern"
class="flex py-8px text-gray-600 text-size-14px leading-24px block"
class="flex py-[8px] text-gray-600 text-[14px] leading-[24px] block"
data-cy="spec-pattern"
>
{{ pattern }}
@@ -11,7 +11,7 @@ describe('<DebugArtifacts />', () => {
it('mounts correctly, provides expected tooltip content, and emits correct event', () => {
artifactMapping.forEach((artifact) => {
cy.mount(() => (
<DebugArtifactLink class="m-24px inline-flex" icon={artifact.icon} popperText={artifact.text} url={artifact.url}/>
<DebugArtifactLink class="m-[24px] inline-flex" icon={artifact.icon} popperText={artifact.text} url={artifact.url}/>
))
cy.findByTestId(`artifact-for-${artifact.icon}`).should('have.length', 1)
@@ -26,7 +26,7 @@ describe('<DebugArtifacts />', () => {
it('mounts correctly for all icons together and has correct URLs', () => {
cy.mount(() => (
<div class="flex flex-grow space-x-4.5 pt-24px justify-center" data-cy='debug-artifacts-all'>
<div class="flex grow space-x-4.5 pt-[24px] justify-center" data-cy='debug-artifacts-all'>
<DebugArtifactLink icon={'TERMINAL_LOG'} popperText={'View Log'} url={'www.cypress.io'}/>
<DebugArtifactLink icon={'IMAGE_SCREENSHOT'} popperText={'View Screenshot'} url={'cloud.cypress.io'}/>
<DebugArtifactLink icon={'PLAY'} popperText={'View Video'} url={'www.cypress.io'}/>
+1 -1
View File
@@ -5,7 +5,7 @@
:distance="8"
>
<ExternalLink
class="flex h-24px w-24px justify-center items-center hocus:rounded-md group hocus:border-1px hocus:border-indigo-500"
class="flex h-[24px] w-[24px] justify-center items-center hocus:rounded-md group hocus:border-[1px] hocus:border-indigo-500"
:data-cy="`${icon}-button`"
:href="props.url || '#'"
:use-default-hocus="true"
@@ -3,23 +3,23 @@
:title="t('debugPage.manuallyCancelled')"
status="warning"
:icon="ErrorOutlineIcon"
class="flex flex-col mb-24px w-full"
class="flex flex-col mb-[24px] w-full"
>
<div class="flex items-center">
<div>{{ t('debugPage.specsSkipped', {n: totalSpecs, totalSkippedSpecs}) }}</div>
<template v-if="cancellation.cancelledBy?.email && cancellation.cancelledBy.fullName">
<div class="rounded-full font-semibold bg-orange-500 h-3px mx-10px w-3px" />
<div class="rounded-full font-semibold bg-orange-500 h-[3px] mx-[10px] w-[3px]" />
<div class="flex items-center">
<UserAvatar
:email="cancellation.cancelledBy.email"
class="h-20px mr-7px w-20px"
class="h-[20px] mr-[7px] w-[20px]"
data-cy="cancelled-by-user-avatar"
/>
<div>{{ cancellation.cancelledBy.fullName }}</div>
</div>
</template>
<template v-if="cancellation.cancelledAt">
<div class="rounded-full font-semibold bg-orange-500 h-3px mx-10px w-3px" />
<div class="rounded-full font-semibold bg-orange-500 h-[3px] mx-[10px] w-[3px]" />
<div>
{{ dayjs(cancellation.cancelledAt).local().format('MMM D, YYYY h:mm A') }}
</div>
+14 -4
View File
@@ -126,7 +126,7 @@ describe('<DebugContainer />', () => {
},
render: (gqlVal) => {
return (
<div class="h-850px">
<div class="h-[850px]">
<DebugContainer
gql={gqlVal}
/>
@@ -191,7 +191,7 @@ describe('<DebugContainer />', () => {
it('handled usage exceeded', () => {
mountTestRun('overLimit')
cy.findByRole('link', { name: 'Contact admin' }).should('have.attr', 'href', 'http://localhost:3000?utmMedium=Debug+Tab&utmSource=Binary%3A+Launchpad')
cy.findByRole('link', { name: 'Contact admin' }).should('be.visible').should('have.attr', 'href', 'http://localhost:3000?utmMedium=Debug+Tab&utmSource=Binary%3A+Launchpad')
cy.percySnapshot()
})
@@ -199,10 +199,20 @@ describe('<DebugContainer />', () => {
it('handles retention exceeded', () => {
mountTestRun('overLimitRetention')
cy.findByRole('link', { name: 'Contact admin' }).should('have.attr', 'href', 'http://localhost:3000?utmMedium=Debug+Tab&utmSource=Binary%3A+Launchpad')
cy.findByRole('link', { name: 'Contact admin' }).should('be.visible').should('have.attr', 'href', 'http://localhost:3000?utmMedium=Debug+Tab&utmSource=Binary%3A+Launchpad')
cy.percySnapshot()
})
it('does not show passing message if run is hidden', () => {
mountTestRun('overLimitPassed')
cy.contains('Well Done!').should('not.exist')
cy.contains('All your tests passed.').should('not.exist')
cy.findByRole('link', { name: 'Contact admin' }).should('be.visible').should('have.attr', 'href', 'http://localhost:3000?utmMedium=Debug+Tab&utmSource=Binary%3A+Launchpad')
})
})
context('cancelled', () => {
@@ -246,7 +256,7 @@ describe('<DebugContainer />', () => {
}
},
render: (gqlVal) =>
<div class="h-850px">
<div class="h-[850px]">
<DebugContainer
gql={gqlVal}
/>
+5 -4
View File
@@ -22,15 +22,16 @@
/>
<div
v-else-if="run?.status"
class="flex flex-col p-1.5rem gap-24px"
class="flex flex-col p-[1.5rem] gap-[24px]"
:class="{'h-full': shouldBeFullHeight}"
>
<DebugRunNavigation
v-if="allRuns && run.runNumber"
class="flex-shrink-0"
class="shrink-0"
:runs="allRuns"
:current-run-number="run.runNumber"
:current-commit-info="currentCommitInfo"
:cloud-project-url="cloudProject?.cloudProjectUrl"
/>
<DebugPageHeader
@@ -41,13 +42,13 @@
<DebugTestingProgress
v-if="isRunning && run.id"
:run-id="run.id"
class="flex-shrink-0"
class="shrink-0"
/>
</TransitionQuickFade>
<DebugPendingRunSplash
v-if="shouldShowPendingRunSplash"
class="flex-grow"
class="grow"
:is-completion-scheduled="isScheduledToComplete"
/>
+4 -4
View File
@@ -3,18 +3,18 @@
:title="t('debugPage.incomplete')"
status="warning"
:icon="ErrorOutlineIcon"
class="flex flex-col mb-24px w-full"
class="flex flex-col mb-[24px] w-full"
>
<div class="ml-5px">
<div class="ml-[5px]">
<ul
v-for="(error, index) in errors"
:key="index"
class="list-disc ml-25px"
class="list-disc ml-[25px]"
>
<li><pre>{{ error }}</pre></li>
</ul>
</div>
<div class="mt-20px">
<div class="mt-[20px]">
{{ t('debugPage.specsSkipped', {n: totalSpecs, totalSkippedSpecs}) }}
</div>
</Alert>
@@ -117,6 +117,12 @@ describe('<DebugFailedTest/>', () => {
cy.findByTestId('test-group').realHover()
cy.findByTestId('debug-artifacts').should('be.visible').children().should('have.length', 3)
cy.findByTestId('debug-artifacts').children().each((artifact) => {
cy.wrap(artifact).find('a').should('have.attr', 'href')
.and('match', /utm_medium/)
.and('match', /utm_campaign/)
.and('match', /utm_source/)
})
cy.percySnapshot()
})
+3 -3
View File
@@ -7,7 +7,7 @@
size="16"
status="failed"
data-cy="failed-icon"
class="min-w-16px isolate"
class="min-w-[16px] isolate"
/>
<template
v-for="{text, type}, index in failedTestData.mappedTitleParts"
@@ -48,7 +48,7 @@
<div
v-if="!props.expandable"
data-cy="debug-artifacts"
class="flex flex-grow opacity-0 px-18px gap-16px justify-end test-row-artifacts"
class="flex grow opacity-0 px-[18px] gap-[16px] justify-end test-row-artifacts"
>
<div
v-for="result, i in failedTestData.debugArtifacts"
@@ -67,7 +67,7 @@
<div
v-if="props.expandable"
data-cy="debug-failed-test-groups"
class="divide-y rounded border-gray-100 border-1"
class="divide-y rounded border-gray-100 border"
>
<GroupedDebugFailedTestVue
:failed-tests="props.failedTestsResult"
+1 -1
View File
@@ -3,7 +3,7 @@
:title="t('debugPage.incomplete')"
status="warning"
:icon="ErrorOutlineIcon"
class="flex flex-col mb-24px w-full"
class="flex flex-col mb-[24px] w-full"
>
{{ t('debugPage.runHasNoTests') }}
</Alert>
+6 -5
View File
@@ -1,17 +1,17 @@
<template>
<div
class="flex flex-col max-w-440px items-center"
class="flex flex-col max-w-[440px] items-center"
>
<LockedProject :class="iconClasses" />
<span class="font-medium mt-24px text-lg text-gray-900">
<span class="font-medium mt-[24px] text-lg text-gray-900">
{{ copy.title }}
</span>
<span class="mt-10px text-center text-gray-600">
<span class="mt-[10px] text-center text-gray-600">
{{ copy.message }}
</span>
<Button
size="lg"
class="mt-25px"
class="mt-[25px]"
:href="actionUrl"
>
{{ copy.actionLabel }}
@@ -28,6 +28,7 @@ import { getUtmSource } from '@packages/frontend-shared/src/utils/getUtmSource'
import { useI18n } from '@cy/i18n'
import { getUrlWithParams } from '@packages/frontend-shared/src/utils/getUrlWithParams'
import { computed } from 'vue'
import { DEBUG_TAB_MEDIUM } from './utils/constants'
export type CloudRunHidingReason = DebugReasonsRunIsHiddenFragment['reasonsRunIsHidden'][number]
@@ -60,7 +61,7 @@ const props = defineProps<{
}>()
const actionUrl = computed(() => {
return getUrlWithParams({ url: props.overLimitActionUrl, params: { utmMedium: 'Debug Tab', utmSource: getUtmSource() } })
return getUrlWithParams({ url: props.overLimitActionUrl, params: { utmMedium: DEBUG_TAB_MEDIUM, utmSource: getUtmSource() } })
})
const overLimitReason = computed<CloudRunHidingReason>(() => {
+2 -2
View File
@@ -20,9 +20,9 @@
/>
<div
v-if="['PASSED', 'OVERLIMIT'].includes(status) || isHidden"
class="flex flex-col flex-grow w-full p-12 justify-center items-center align-middle "
class="flex flex-col grow w-full p-12 justify-center items-center align-middle "
>
<DebugPassed v-if="status === 'PASSED'" />
<DebugPassed v-if="status === 'PASSED' && !isHidden" />
<DebugOverLimit
v-if="isHidden"
:over-limit-reasons="reasonsRunIsHidden"
@@ -37,6 +37,8 @@ describe('<DebugPageHeader />', {
cy.findByTestId('debug-header').children().should('have.length', 2)
cy.findByTestId('debug-test-summary').should('have.text', 'Adding a hover state to the button component')
cy.findByTestId('debug-header-dashboard-link').should('be.visible').should('have.attr', 'href', 'http://dummy.cypress.io/runs/1?utm_medium=Debug+Tab&utm_campaign=View+in+Cypress+Cloud&utm_source=Binary%3A+Launchpad')
cy.findByTestId('debug-commitsAhead').should('have.text', 'You are 2 commits ahead')
cy.findByTestId('debug-results').should('be.visible')
+15 -9
View File
@@ -1,7 +1,7 @@
<template>
<div
data-cy="debug"
class="flex flex-col gap-16px"
class="flex flex-col gap-[16px]"
>
<div
data-cy="debug-header"
@@ -13,7 +13,7 @@
<li
v-if="debug?.commitInfo?.summary"
class="font-medium text-lg text-gray-900 inline"
:class="{'mr-8px': props.commitsAhead}"
:class="{'mr-[8px]': props.commitsAhead}"
data-cy="debug-test-summary"
>
{{ debug.commitInfo.summary }}
@@ -30,7 +30,7 @@
</span>
</li>
<li
class="text-lg text-gray-400 w-16px inline"
class="text-lg text-gray-400 w-[16px] inline"
aria-hidden="true"
>
@@ -38,7 +38,7 @@
<li class="font-normal text-sm text-indigo-500 inline">
<ExternalLink
data-cy="debug-header-dashboard-link"
:href="debug.url || '#'"
:href="runUrl"
>
{{ t('debugPage.header.runUrl') }}
</ExternalLink>
@@ -65,7 +65,7 @@
v-if="debug?.commitInfo?.branch"
data-cy="debug-header-branch"
>
<i-cy-tech-branch-h_x16 class="mr-1 mr-8px icon-dark-gray-300" />
<i-cy-tech-branch-h_x16 class="mr-2 icon-dark-gray-300" />
<span class="sr-only">Branch Name:</span> {{ debug.commitInfo.branch }}
</li>
<li
@@ -73,7 +73,7 @@
data-cy="debug-header-commitHash"
>
<CommitIcon
class="h-16px fill-white mr-11px w-16px"
class="h-[16px] fill-white mr-2 w-[16px]"
/>
<span class="sr-only">Commit Hash:</span> {{ debug.commitInfo?.sha?.substring(0,7) }}
</li>
@@ -82,7 +82,7 @@
data-cy="debug-header-author"
>
<UserAvatar
class="h-16px mr-8px w-16px"
class="h-[16px] mr-2 w-[16px]"
:email="debug?.commitInfo?.authorEmail"
data-cy="debug-header-avatar"
/>
@@ -94,7 +94,7 @@
>
<IconTimeStopwatch
size="16"
class="mr-9px"
class="mr-2"
stroke-color="gray-500"
fill-color="gray-50"
/>
@@ -116,6 +116,8 @@ import { useI18n } from 'vue-i18n'
import DebugRunNumber from './DebugRunNumber.vue'
import UserAvatar from '@cy/gql-components/topnav/UserAvatar.vue'
import { useRunDateTimeInterval } from './useRunDateTimeInterval'
import { getUrlWithParams } from '@packages/frontend-shared/src/utils/getUrlWithParams'
import { DEBUG_TAB_MEDIUM } from './utils/constants'
const { t } = useI18n()
@@ -147,12 +149,16 @@ const props = defineProps<{
const debug = computed(() => props.gql)
const runUrl = computed(() => {
return debug.value.url ? getUrlWithParams({ url: debug.value.url, params: { utm_medium: DEBUG_TAB_MEDIUM, utm_campaign: 'View in Cypress Cloud' } }) : '#'
})
const { relativeCreatedAt, totalDuration } = useRunDateTimeInterval(debug)
</script>
<style scoped>
[data-cy=metadata] li:not(:first-child)::before {
content: '•';
@apply text-lg text-gray-400 pr-8px
@apply text-lg text-gray-400 pr-[8px]
}
</style>
+2 -2
View File
@@ -4,10 +4,10 @@
data-cy="debug-passed"
>
<DashboardCheckmark class="icon-dark-gray-500" />
<span class="font-medium mt-24px text-gray-900">
<span class="font-medium mt-[24px] text-gray-900">
{{ t('debugPage.wellDone') }}
</span>
<span class="mt-5px text-gray-700">{{ t('debugPage.allYourTestsPassed') }}</span>
<span class="mt-[5px] text-gray-700">{{ t('debugPage.allYourTestsPassed') }}</span>
</div>
</template>
@@ -4,7 +4,7 @@
class="flex p-12"
>
<div class="flex flex-col w-full items-center justify-center">
<IconTechnologyDashboardRunning class="mb-16px" />
<IconTechnologyDashboardRunning class="mb-[16px]" />
<span
data-cy="title"
class="font-medium text-lg text-gray-900"
@@ -14,7 +14,7 @@
<div
v-if="!isCompletionScheduled"
data-cy="splash-subtitle"
class="font-normal mt-4px text-md text-gray-600"
class="font-normal mt-[4px] text-md text-gray-600"
>
{{ t('debugPage.pending.failuresHere') }}
</div>
+3 -3
View File
@@ -1,6 +1,6 @@
<template>
<div
class="flex gap-8px items-center"
class="flex gap-[8px] items-center"
data-cy="debug-results-holder"
>
<ResultCounts
@@ -13,7 +13,7 @@
<div
v-if="results?.totalFlakyTests"
data-cy="debug-flaky-badge"
class="border rounded flex flex-row gap-8px items-center h-6 bg-orange-50 border-orange-200 text-sm text-orange-600 px-2 gap-x-1 border-1"
class="border rounded flex flex-row gap-[8px] items-center h-6 bg-orange-50 border-orange-200 text-sm text-orange-600 px-2 gap-x-1 border"
>
<span
data-cy="total-flaky-tests"
@@ -21,7 +21,7 @@
>
{{ results.totalFlakyTests }}
</span>
<div class="w-px my-6px h-6 border-orange-200 border" />
<div class="w-px my-[6px] h-6 border-orange-200 border" />
<span class="font-semibold pl-1">
{{ t('specPage.flaky.badgeLabel') }}
</span>
@@ -12,6 +12,7 @@ function mountDebugDetailedView (data: {
currentRun: ReturnType<typeof createRun>
allRuns: Array<ReturnType<typeof createRun>>
currentCommitInfo?: CommitInfo
cloudProjectUrl?: string
}) {
return cy.mountFragment(DebugRunNavigationFragmentDoc, {
variableTypes: DebugSpecVariableTypes,
@@ -25,7 +26,7 @@ function mountDebugDetailedView (data: {
render (gqlData) {
return (
<div style="margin: 10px">
<DebugRunNavigation runs={gqlData.allRuns!} currentRunNumber={data.currentRun.runNumber!} currentCommitInfo={data.currentCommitInfo}/>
<DebugRunNavigation runs={gqlData.allRuns!} currentRunNumber={data.currentRun.runNumber!} currentCommitInfo={data.currentCommitInfo} cloudProjectUrl={data.cloudProjectUrl} />
</div>
)
},
@@ -56,6 +57,7 @@ describe('<DebugRunNavigation />', () => {
cy.get('[data-cy="debug-toggle"]').click()
cy.findByTestId('current-run').should('exist')
cy.contains('We found more than 100 runs.').should('not.exist')
})
it('hide toggle if not on latest and only two runs', () => {
@@ -138,6 +140,8 @@ describe('<DebugRunNavigation />', () => {
cy.get('[data-cy="debug-toggle"]').click()
cy.contains('We found more than 100 runs.').should('not.exist')
cy.findByTestId('commit-sha-123').as('commit-123').should('exist')
cy.get('@commit-123').contains('sha-123')
cy.get('@commit-123').contains('add new feature with a really long commit message to see what happens')
@@ -163,7 +167,12 @@ describe('<DebugRunNavigation />', () => {
})
it('renders correctly in several sizes', () => {
//pausing time to prevent Percy flake
cy.clock(new Date())
cy.get('[data-cy="debug-toggle"]').click()
cy.tick(2 * 1000) //allow toggle to animate
cy.contains('We found more than 100 runs.').should('not.exist')
cy.viewport(616, 850) //currently the narrowest the parent component will go
cy.percySnapshot('narrowest')
@@ -202,6 +211,40 @@ describe('<DebugRunNavigation />', () => {
})
})
it('displays the limit message when the number of total runs is exactly 100', () => {
const latest = createRun({
runNumber: 99,
status: 'RUNNING',
sha: 'sha-123',
summary: 'add new feature with a really long commit message to see what happens',
completedInstanceCount: 5,
totalInstanceCount: 12,
})
const other1 = createRun({ runNumber: 98, status: 'PASSED', sha: 'sha-456', summary: 'Update code' })
const other2 = createRun({ runNumber: 97, status: 'PASSED', sha: 'sha-456', summary: 'Update code' })
const allRuns = [other1, other2]
for (let i = 96; i >= 0; i--) {
allRuns.push(createRun({ runNumber: i, status: 'PASSED', sha: 'sha-456', summary: 'Update code' }))
}
const commitInfo = { sha: 'sha-123', message: 'add new feature with a really long commit message to see what happens' } as CommitInfo
mountDebugDetailedView({ currentRun: other1, allRuns: [latest, ...allRuns], currentCommitInfo: commitInfo, cloudProjectUrl: 'https://cloud.cypress.io/projects/ypt4pf/' })
// This should only show when the list is expanded
cy.contains('We found more than 100 runs.').should('not.exist')
cy.findByTestId('debug-toggle').click()
cy.contains('We found more than 100 runs.').should('be.visible')
cy.findByRole('link', { name: 'Go to Cypress Cloud to see all runs' }).should('be.visible').should('have.attr', 'href', 'https://cloud.cypress.io/projects/ypt4pf/?utm_medium=Debug+Tab&utm_campaign=Run+Navigation+Limit&utm_source=Binary%3A+Launchpad')
cy.percySnapshot()
})
describe('Switch to latest run button', () => {
it('shows "Switch to latest run" when current is not latest', () => {
const latest = createRun({ runNumber: 3, status: 'PASSED', sha: 'sha-123', summary: 'Update code #2' })
+65 -57
View File
@@ -1,21 +1,21 @@
<template>
<div
v-if="shouldShow"
class="border rounded border-indigo-100 overflow-hidden"
class="overflow-hidden border border-indigo-100 rounded"
>
<div
class="bg-indigo-50 p-12px group"
class="bg-indigo-50 p-[12px] group"
data-cy="debug-detailed-header"
>
<div
class="flex items-center justify-between"
>
<div class="flex min-w-0 items-center">
<div class="flex items-center min-w-0">
<button
v-if="!hideToggle"
:aria-expanded="showRuns"
aria-controls="debug-runs-container"
class="border border-transparent rounded flex p-2px transition items-center hocus-default hover:bg-white focus:bg-white active:bg-white group-hover:(outline outline-indigo-100) "
class="border border-transparent rounded flex p-[2px] transition items-center hocus-default hover:bg-white focus:bg-white active:bg-white group-hover:outline group-hover:outline-indigo-100"
data-cy="debug-toggle"
@click="toggleRuns()"
>
@@ -25,7 +25,7 @@
stroke-color="indigo-400"
/>
<span
class="font-medium text-sm mr-4px ml-8px text-indigo-500"
class="font-medium text-sm mr-[4px] ml-[8px] text-indigo-500"
:class="{'sr-only': !latestIsCurrentlySelected}"
>
{{ t('debugPage.switchRun') }}
@@ -41,25 +41,25 @@
<DebugRunNumber
:status="latest.status"
:value="latest.runNumber"
class="mx-8px"
class="mx-[8px]"
/>
<DebugResults
v-if="latest"
:gql="latest"
class="bg-white mr-12px"
class="bg-white mr-[12px]"
/>
<span
class="font-medium text-gray-800 truncate"
:title="latest.commitInfo?.summary!"
>{{ latest.commitInfo?.summary }}</span>
<Dot class="hidden lg:block" />
<span class="flex-shrink-0 text-gray-700 truncate hidden lg:block">{{ specsCompleted(latest) }}</span>
<span class="hidden text-gray-700 truncate shrink-0 lg:block">{{ specsCompleted(latest) }}</span>
</template>
</div>
<Button
v-if="!latestIsCurrentlySelected"
data-cy="switch-to-latest"
class="flex-shrink-0 ml-8px"
class="shrink-0 ml-[8px]"
@click="$event => changeRun(latest!)"
>
{{ t('debugPage.switchToLatestRun') }}
@@ -68,56 +68,61 @@
</div>
<TransitionQuickFadeVue>
<div
v-if="showRuns"
id="debug-runs-container"
class="max-h-30vh overflow-y-scroll"
data-cy="debug-runs-container"
>
<ul
class="my-8px relative before:(content-DEFAULT top-20px bottom-10px w-5px border-2 border-dashed border-l-0 border-y-0 border-r-gray-100 left-[19px] absolute) "
data-cy="debug-historical-runs"
<div v-if="showRuns">
<div
id="debug-runs-container"
class="overflow-y-scroll max-h-[30vh]"
data-cy="debug-runs-container"
>
<li
v-for="sha of Object.keys(groupByCommit)"
:key="sha"
:data-cy="`commit-${sha}`"
<ul
class="my-[8px] relative before:content-[''] before:absolute before:top-[20px] before:bottom-[10px] before:w-[5px] before:border-2 before:border-dashed before:border-l-0 before:border-y-0 before:border-r-gray-100 before:left-[19px]"
data-cy="debug-historical-runs"
>
<div class="flex my-10px mx-16px items-center">
<DebugCommitIcon class="flex-shrink-0" />
<LightText class="flex-shrink-0 ml-12px truncate">
{{ sha.slice(0, 7) }}
</LightText>
<Dot />
<span
class="font-medium text-sm text-gray-800 truncate"
:title="groupByCommit[sha].message!"
>
{{ groupByCommit[sha].message }}
</span>
<span
v-if="sha === currentCommitInfo?.sha"
data-cy="tag-checked-out"
class="border rounded font-medium border-gray-100 border-1 flex-shrink-0
h-16px ml-8px px-4px text-12px text-purple-400 leading-16px
align-middle inline-flex items-center"
>
Checked out
</span>
</div>
<li
v-for="sha of Object.keys(groupByCommit)"
:key="sha"
:data-cy="`commit-${sha}`"
>
<div class="flex items-center my-[10px] mx-[16px]">
<DebugCommitIcon class="shrink-0" />
<LightText class="shrink-0 truncate ml-[12px]">
{{ sha.slice(0, 7) }}
</LightText>
<Dot />
<span
class="text-sm font-medium text-gray-800 truncate"
:title="groupByCommit[sha].message!"
>
{{ groupByCommit[sha].message }}
</span>
<span
v-if="sha === currentCommitInfo?.sha"
data-cy="tag-checked-out"
class="inline-flex items-center shrink-0 font-medium text-purple-400 align-middle border border-gray-100 rounded border-1 h-[16px] ml-[8px] px-[4px] text-[12px] leading-[16px]"
>
Checked out
</span>
</div>
<ul v-if="groupByCommit[sha].runs">
<DebugRunNavigationRow
v-for="run of groupByCommit[sha].runs"
:key="run?.runNumber!"
:run-number="run?.runNumber!"
:is-current-run="isCurrentRun(run!)"
:gql="run!"
@change-run="changeRun(run!)"
/>
</ul>
</li>
</ul>
<ul v-if="groupByCommit[sha].runs">
<DebugRunNavigationRow
v-for="run of groupByCommit[sha].runs"
:key="run?.runNumber!"
:run-number="run?.runNumber!"
:is-current-run="isCurrentRun(run!)"
:gql="run!"
@change-run="changeRun(run!)"
/>
</ul>
</li>
</ul>
</div>
<div
v-if="runs.length === 100"
class="border-t border-indigo-100"
>
<DebugRunNavigationLimitMessage :cloud-project-url="cloudProjectUrl" />
</div>
</div>
</TransitionQuickFadeVue>
</div>
@@ -133,6 +138,7 @@ import { DebugRunNavigationFragment, DebugRunNavigationRunInfoFragment, DebugRun
import DebugResults from './DebugResults.vue'
import DebugRunNumber from './DebugRunNumber.vue'
import DebugCommitIcon from './DebugCommitIcon.vue'
import DebugRunNavigationLimitMessage from './DebugRunNavigationLimitMessage.vue'
import { IconChevronRightSmall } from '@cypress-design/vue-icon'
import { useDebugRunSummary } from './useDebugRunSummary'
import { useI18n } from '@cy/i18n'
@@ -173,6 +179,7 @@ fragment DebugRunNavigationRunInfo on CloudRun {
gql`
fragment DebugRunNavigation on CloudProject {
id
cloudProjectUrl
allRuns: runsByCommitShas(commitShas: $commitShas) {
id
...DebugRunNavigationRunInfo
@@ -189,11 +196,12 @@ mutation DebugRunNavigation_moveToRun($runNumber: Int!) {
const props = defineProps<{
runs: NonNullable<DebugRunNavigationFragment['allRuns']>
currentRunNumber: number
cloudProjectUrl?: string
currentCommitInfo?: { sha: string, message: string } | null
}>()
const Dot: FunctionalComponent = () => {
return h('span', { class: 'px-8px text-gray-300' }, '•')
return h('span', { class: 'px-[8px] text-gray-300' }, '•')
}
const LightText: FunctionalComponent = (_props, { slots }) => {
@@ -0,0 +1,17 @@
import DebugRunNavigationLimitMessage from './DebugRunNavigationLimitMessage.vue'
describe('DebugRunNavigationLimitMessage', () => {
it('renders link if cloudProjectUrl is passed', () => {
cy.mount(<DebugRunNavigationLimitMessage cloudProjectUrl="https://cloud.cypress.io/projects/ypt4pf/" />)
cy.findByRole('link', { name: 'Go to Cypress Cloud to see all runs' }).should('be.visible').should('have.attr', 'href', 'https://cloud.cypress.io/projects/ypt4pf/?utm_medium=Debug+Tab&utm_campaign=Run+Navigation+Limit&utm_source=Binary%3A+Launchpad')
cy.percySnapshot()
})
it('does not render link if cloudProjectUrl is falsy', () => {
cy.mount(<DebugRunNavigationLimitMessage cloudProjectUrl="" />)
cy.findByRole('link', { name: 'Go to Cypress Cloud to see all runs' }).should('not.exist')
})
})

Some files were not shown because too many files have changed in this diff Show More