Merge branch 'develop' into cache-sessions

# Conflicts:
#	packages/app/src/runner/event-manager.ts
#	packages/driver/cypress.config.ts
#	packages/driver/src/cy/commands/sessions/index.ts
#	packages/reporter/src/sessions/sessions.tsx
#	packages/server/lib/project-base.ts
#	packages/server/lib/server-base.ts
#	system-tests/test/session_spec.ts
This commit is contained in:
Emily Rohrbough
2022-09-15 11:19:14 -05:00
532 changed files with 30092 additions and 12084 deletions

View File

@@ -0,0 +1,3 @@
# Bump this version to force CI to re-create the cache from scratch.
9-13-22

View File

@@ -27,6 +27,7 @@ mainBuildFilters: &mainBuildFilters
branches:
only:
- develop
- fix-ci-deps
# 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
@@ -35,8 +36,7 @@ macWorkflowFilters: &darwin-workflow-filters
when:
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ 'tbiethman/22272-globbing-working-dir', << pipeline.git.branch >> ]
- equal: [ 'skip-or-fix-flaky-tests-2', << pipeline.git.branch >> ]
- equal: [ 'correct-dashboard-results', << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>
@@ -45,8 +45,6 @@ linuxArm64WorkflowFilters: &linux-arm64-workflow-filters
when:
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ "lmiller/experimental-single-tab-component-testing", << pipeline.git.branch >> ]
- equal: [ 'skip-or-fix-flaky-tests-2', << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>
@@ -66,8 +64,7 @@ windowsWorkflowFilters: &windows-workflow-filters
or:
- equal: [ develop, << pipeline.git.branch >> ]
- equal: [ linux-arm64, << pipeline.git.branch >> ]
- equal: [ 'marktnoonan/windows-path-fix', << pipeline.git.branch >> ]
- equal: [ 'skip-or-fix-flaky-tests-2', << pipeline.git.branch >> ]
- equal: [ 'lmiller/fixing-flake-1', << pipeline.git.branch >> ]
- matches:
pattern: "-release$"
value: << pipeline.git.branch >>
@@ -132,7 +129,7 @@ commands:
- run:
name: Check current branch to persist artifacts
command: |
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "tbiethman/22272-globbing-working-dir" ]]; then
if [[ "$CIRCLE_BRANCH" != "develop" && "$CIRCLE_BRANCH" != "webkit-experimental" ]]; then
echo "Not uploading artifacts or posting install comment for this branch."
circleci-agent step halt
fi
@@ -178,6 +175,14 @@ commands:
mv ~/cypress/system-tests/node_modules /tmp/node_modules_cache/system-tests_node_modules
mv ~/cypress/globbed_node_modules /tmp/node_modules_cache/globbed_node_modules
install-webkit-deps:
steps:
- run:
name: Install WebKit dependencies
command: |
npx playwright install webkit
npx playwright install-deps webkit
build-and-persist:
description: Save entire folder as artifact for other jobs to run without reinstalling
steps:
@@ -219,7 +224,7 @@ commands:
command: node ./scripts/get-platform-key.js > platform_key
- restore_cache:
name: Restore cache state, to check for known modules cache existence
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-node-modules-cache-{{ checksum "circle_cache_key" }}
key: v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-node-modules-cache-{{ checksum "circle_cache_key" }}
- run:
name: Move node_modules back from /tmp
command: |
@@ -246,7 +251,7 @@ commands:
- restore_cache:
name: Restore system tests node_modules cache
keys:
- v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
- v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
update_cached_system_tests_deps:
description: 'Update the cached node_modules for projects in "system-tests/projects/**"'
@@ -260,7 +265,7 @@ commands:
- restore_cache:
name: Restore cache state, to check for known modules cache existence
keys:
- v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-state-of-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
- v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-state-of-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
- run:
name: Send root honeycomb event for this CI build
command: cd system-tests/scripts && node ./send-root-honeycomb-event.js
@@ -274,20 +279,20 @@ commands:
- restore_cache:
name: Restore system tests node_modules cache
keys:
- v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
- v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-
- v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
- v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-
- run:
name: Update system-tests node_modules cache
command: yarn workspace @tooling/system-tests projects:yarn:install
- save_cache:
name: Save system tests node_modules cache
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
key: v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
paths:
- /tmp/cy-system-tests-node-modules
- run: touch /tmp/system_tests_node_modules_installed
- save_cache:
name: Save system tests node_modules cache state key
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-state-of-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
key: v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-state-of-system-tests-projects-node-modules-cache-{{ checksum "system_tests_cache_key" }}
paths:
- /tmp/system_tests_node_modules_installed
@@ -307,7 +312,7 @@ commands:
command: node ./scripts/get-platform-key.js > platform_key
- restore_cache:
name: Restore cache state, to check for known modules cache existence
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-state-of-node-modules-cache-{{ checksum "circle_cache_key" }}
key: v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-state-of-node-modules-cache-{{ checksum "circle_cache_key" }}
- run:
name: Bail if cache exists
command: |
@@ -319,7 +324,7 @@ commands:
- restore_cache:
name: Restore weekly yarn cache
keys:
- v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-deps-root-weekly-{{ checksum "cache_date" }}
- v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-deps-root-weekly-{{ checksum "cache_date" }}
- run:
name: Install Node Modules
command: |
@@ -336,7 +341,7 @@ commands:
steps:
- save_cache:
name: Saving node modules for root, cli, and all globbed workspace packages
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-node-modules-cache-{{ checksum "circle_cache_key" }}
key: v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-node-modules-cache-{{ checksum "circle_cache_key" }}
paths:
- node_modules
- cli/node_modules
@@ -347,18 +352,18 @@ commands:
steps:
- save_cache:
name: Saving node modules for root, cli, and all globbed workspace packages
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-node-modules-cache-{{ checksum "circle_cache_key" }}
key: v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-node-modules-cache-{{ checksum "circle_cache_key" }}
paths:
- /tmp/node_modules_cache
- run: touch node_modules_installed
- save_cache:
name: Saving node-modules cache state key
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-state-of-node-modules-cache-{{ checksum "circle_cache_key" }}
key: v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-state-of-node-modules-cache-{{ checksum "circle_cache_key" }}
paths:
- node_modules_installed
- save_cache:
name: Save weekly yarn cache
key: v{{ .Environment.CACHE_VERSION }}-{{ checksum "platform_key" }}-deps-root-weekly-{{ checksum "cache_date" }}
key: v{{ checksum ".circleci/cache-version.txt" }}-{{ checksum "platform_key" }}-deps-root-weekly-{{ checksum "cache_date" }}
paths:
- ~/.yarn
- ~/.cy-npm-cache
@@ -462,6 +467,11 @@ commands:
- install-chrome:
channel: <<parameters.install-chrome-channel>>
version: $(node ./scripts/get-browser-version.js chrome:<<parameters.install-chrome-channel>>)
- when:
condition:
equal: [ webkit, << parameters.browser >> ]
steps:
- install-webkit-deps
- run:
name: Run driver tests in Cypress
environment:
@@ -470,11 +480,6 @@ commands:
echo Current working directory is $PWD
echo Total containers $CIRCLE_NODE_TOTAL
if [[ "<<parameters.browser>>" = "webkit" ]]; then
npx playwright install webkit
npx playwright install-deps webkit
fi
if [[ -v MAIN_RECORD_KEY ]]; then
# internal PR
if <<parameters.experimentalSessionAndOrigin>>; then
@@ -610,6 +615,11 @@ commands:
steps:
- restore_cached_workspace
- restore_cached_system_tests_deps
- when:
condition:
equal: [ webkit, << parameters.browser >> ]
steps:
- install-webkit-deps
- run:
name: Run system tests
command: |
@@ -1272,9 +1282,12 @@ jobs:
run-webpack-dev-server-integration-tests,
run-vite-dev-server-integration-tests
- run:
# Sometimes, even though all the circle jobs have finished, Percy times out during `build:finalize`
# If all other jobs finish but `build:finalize` fails, we retry it once
name: Finalize percy build - allows single retry
command: |
PERCY_PARALLEL_NONCE=$CIRCLE_WORKFLOW_WORKSPACE_ID \
yarn percy build:finalize
yarn percy build:finalize || yarn percy build:finalize
cli-visual-tests:
<<: *defaults
@@ -1324,7 +1337,7 @@ jobs:
steps:
- run: yarn test-scripts
# make sure packages with TypeScript can be transpiled to JS
- run: yarn lerna run build-prod --stream
- run: yarn lerna run build-prod --stream --concurrency 4
# run unit tests from each individual package
- run: yarn test
# run type checking for each individual package
@@ -1448,6 +1461,13 @@ jobs:
- run-system-tests:
browser: firefox
system-tests-webkit:
<<: *defaults
parallelism: 8
steps:
- run-system-tests:
browser: webkit
system-tests-non-root:
<<: *defaults
steps:
@@ -1831,7 +1851,6 @@ jobs:
npm-react:
<<: *defaults
parallelism: 8
steps:
- restore_cached_workspace
- run:
@@ -2320,7 +2339,7 @@ linux-x64-workflow: &linux-x64-workflow
requires:
- build
- percy-finalize:
context: test-runner:poll-circle-workflow
context: [test-runner:poll-circle-workflow, test-runner:percy]
required_env_var: PERCY_TOKEN # skips job if not defined (external PR)
requires:
- build
@@ -2364,6 +2383,10 @@ linux-x64-workflow: &linux-x64-workflow
context: test-runner:performance-tracking
requires:
- system-tests-node-modules-install
- system-tests-webkit:
context: test-runner:performance-tracking
requires:
- system-tests-node-modules-install
- system-tests-non-root:
context: test-runner:performance-tracking
executor: non-root-docker-user

33
.github/workflows/snyk_sca_scan.yaml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Snyk Software Composition Analysis Scan
# This git workflow leverages Snyk actions to perform a Software Composition
# Analysis scan on our Opensource libraries upon Pull Requests to Master &
# Develop branches. We use this as a control to prevent vulnerable packages
# from being introduced into the codebase.
on:
pull_request_target:
types:
- opened
branches:
- master
- develop
jobs:
Snyk_SCA_Scan:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps:
- uses: actions/checkout@v2
- name: Setting up Node
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Installing snyk-delta and dependencies
run: npm i -g snyk-delta
- uses: snyk/actions/setup@master
- name: Perform SCA Scan
continue-on-error: false
run: |
snyk test --yarn-workspaces --strict-out-of-sync=false --detection-depth=6 --exclude=docker,Dockerfile --severity-threshold=critical
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

View File

@@ -0,0 +1,29 @@
name: Snyk Static Analysis Scan
# This git workflow leverages Snyk actions to perform a Static Application
# Testing scan (SAST) on our first-party code upon Pull Requests to Master &
# Develop branches. We use this as a control to prevent vulnerabilities
# from being introduced into the codebase.
on:
pull_request_target:
types:
- opened
branches:
- master
- develop
jobs:
Snyk_SAST_Scan :
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: snyk/actions/setup@master
- name: Perform Static Analysis Test
continue-on-error: true
run: |
snyk code test --yarn-workspaces --strict-out-of-sync=false --detection-depth=6 --exclude=docker,Dockerfile --severity-threshold=high
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# The Following Requires Advanced Security License
# - name: Upload results to Github Code Scanning
# uses: github/codeql-action/upload-sarif@v1
# with:
# sarif_file: snyk_sarif

View File

@@ -1,10 +1,10 @@
name: Add issue/PR to project
name: 'Triage: add issue/PR to project'
on:
issues:
types:
- opened
pull_request:
pull_request_target:
types:
- opened

View File

@@ -0,0 +1,39 @@
name: 'Triage: route to team project board'
on:
issues:
types:
- labeled
jobs:
route-to-e2e:
if: github.event.label.name == 'routed-to-e2e'
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_TOKEN }}
ORGANIZATION: 'cypress-io'
PROJECT_NUMBER: 10
run: |
gh api graphql -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectV2(number: $number) {
id
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq -r '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
- name: add issue to e2e project
env:
GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_TOKEN }}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
gh api graphql -f query='
mutation($project:ID!, $issue:ID!) {
addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) {
item {
id
}
}
}' -f project=$PROJECT_ID -f issue=$ISSUE_ID

View File

@@ -0,0 +1,93 @@
name: 'Triage: closed issue comment'
on:
issue_comment:
types:
- created
jobs:
move-to-new-issue-status:
if: |
!github.event.issue.pull_request &&
github.event.issue.state == 'closed' &&
github.event.comment.created_at != github.event.issue.closed_at &&
github.event.sender.login != 'cypress-bot'
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_TOKEN }}
ORGANIZATION: 'cypress-io'
REPOSITORY: 'cypress'
PROJECT_NUMBER: 9
ISSUE_NUMBER: ${{ github.event.issue.number }}
run: |
gh api graphql -f query='
query($org: String!, $repo: String!, $project: Int!, $issue: Int!) {
organization(login: $org) {
repository(name: $repo) {
issue(number: $issue) {
projectItems(first: 10, includeArchived: false) {
nodes {
id
fieldValueByName(name: "Status") {
... on ProjectV2ItemFieldSingleSelectValue {
name
field {
... on ProjectV2SingleSelectField {
project {
... on ProjectV2 {
id
number
}
}
}
}
}
}
}
}
}
}
projectV2(number: $project) {
field(name: "Status") {
... on ProjectV2SingleSelectField {
id
options {
id
name
}
}
}
}
}
}' -f org=$ORGANIZATION -f repo=$REPOSITORY -F issue=$ISSUE_NUMBER -F project=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq -r '.data.organization.repository.issue.projectItems.nodes[].fieldValueByName.field.project | select(.number == ${{ env.PROJECT_NUMBER }}) | .id' project_data.json) >> $GITHUB_ENV
echo 'PROJECT_ITEM_ID='$(jq -r '.data.organization.repository.issue.projectItems.nodes[] | select(.fieldValueByName.field.project.number == ${{ env.PROJECT_NUMBER }}) | .id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq -r '.data.organization.projectV2.field | .id' project_data.json) >> $GITHUB_ENV
echo 'STATUS='$(jq -r '.data.organization.repository.issue.projectItems.nodes[].fieldValueByName | select(.field.project.number == ${{ env.PROJECT_NUMBER }}) | .name' project_data.json) >> $GITHUB_ENV
echo 'NEW_ISSUE_OPTION_ID='$(jq -r '.data.organization.projectV2.field.options[] | select(.name== "New Issue") | .id' project_data.json) >> $GITHUB_ENV
- name: Move issue to New Issue status
env:
GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_TOKEN }}
if: env.STATUS == 'Closed'
run: |
gh api graphql -f query='
mutation (
$project: ID!
$item: ID!
$status_field: ID!
$status_value: String!
) {
updateProjectV2ItemFieldValue(input: {
projectId: $project
itemId: $item
fieldId: $status_field
value: {
singleSelectOptionId: $status_value
}
}) {
projectV2Item {
id
}
}
}' -f project=$PROJECT_ID -f item=$PROJECT_ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=$NEW_ISSUE_OPTION_ID

View File

@@ -0,0 +1,114 @@
name: 'Triage: issue metrics'
on:
workflow_dispatch:
inputs:
startDate:
description: 'Start date (YYYY-MM-DD)'
type: date
endDate:
description: 'End date (YYYY-MM-DD)'
type: date
jobs:
seven-day-close:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6
env:
ORGANIZATION: 'cypress-io'
REPOSITORY: 'cypress'
PROJECT_NUMBER: 9
with:
github-token: ${{ secrets.ADD_TO_PROJECT_TOKEN }}
script: |
const ROUTED_TO_LABELS = ['routed-to-e2e', 'routed-to-ct']
const MS_PER_DAY = 1000 * 60 * 60 * 24
const { REPOSITORY, ORGANIZATION, PROJECT_NUMBER } = process.env
const issues = []
const determineDateRange = () => {
const inputStartDate = '${{ inputs.startDate }}'
const inputEndDate = '${{ inputs.endDate }}'
if (inputStartDate && inputEndDate) {
return { startDate: inputStartDate, endDate: inputEndDate }
}
if (inputStartDate || inputEndDate) {
core.setFailed('Both startDate and endDate are required if one is provided.')
}
const startDate = new Date()
startDate.setDate(startDate.getDate() - 6)
return { startDate: startDate.toISOString().split('T')[0], endDate: (new Date()).toISOString().split('T')[0] }
}
const dateRange = determineDateRange()
const query = `is:issue+repo:${ORGANIZATION}/${REPOSITORY}+project:${ORGANIZATION}/${PROJECT_NUMBER}+created:${dateRange.startDate}..${dateRange.endDate}`
const findLabelDateTime = async (issueNumber) => {
const iterator = github.paginate.iterator(github.rest.issues.listEventsForTimeline, {
owner: ORGANIZATION,
repo: REPOSITORY,
issue_number: issueNumber,
})
for await (const { data: timelineData } of iterator) {
for (const timelineItem of timelineData) {
if (timelineItem.event === 'labeled' && ROUTED_TO_LABELS.includes(timelineItem.label.name)) {
return timelineItem.created_at
}
}
}
}
const calculateElapsedDays = (createdAt, routedOrClosedAt) => {
return Math.round((new Date(routedOrClosedAt) - new Date(createdAt)) / MS_PER_DAY, 0)
}
const iterator = github.paginate.iterator(github.rest.search.issuesAndPullRequests, {
q: query,
per_page: 100,
})
for await (const { data } of iterator) {
for (const issue of data) {
let routedOrClosedAt
if (!issue.pull_request) {
const routedLabel = issue.labels.find((label) => ROUTED_TO_LABELS.includes(label.name))
if (routedLabel) {
routedOrClosedAt = await findLabelDateTime(issue.number)
} else if (issue.state === 'closed') {
routedOrClosedAt = issue.closed_at
}
let elapsedDays
if (routedOrClosedAt) {
elapsedDays = calculateElapsedDays(issue.created_at, routedOrClosedAt)
}
issues.push({
number: issue.number,
title: issue.title,
state: issue.state,
url: issue.html_url,
createdAt: issue.created_at,
routedOrClosedAt,
elapsedDays,
})
}
}
}
const issuesRoutedOrClosedIn7Days = issues.filter((issue) => issue.elapsedDays <= 7).length
const percentage = Number(issues.length > 0 ? issuesRoutedOrClosedIn7Days / issues.length : 0).toLocaleString(undefined, { style: 'percent', minimumFractionDigits: 2 })
console.log(`Triage Metrics (${dateRange.startDate} - ${dateRange.endDate})`)
console.log('Total issues:', issues.length)
console.log(`Issues routed/closed within 7 days: ${issuesRoutedOrClosedIn7Days} (${percentage})`)

View File

@@ -461,7 +461,7 @@ There are also a set of system tests in [`system-tests`](system-tests) which att
Additionally, we test the code by running it against various other example projects in CI. See CI badges and links at the top of this document.
If you're curious how we manage all of these tests in CI check out our [`circle.yml`](circle.yml) file found in the root `cypress` directory.
If you're curious how we manage all of these tests in CI check out our [CircleCI config](.circleci/config.yml).
#### Docker
@@ -474,7 +474,7 @@ Sometimes tests pass locally, but fail in CI. Our CI environment is dockerized.
$ yarn docker
```
There is a script [scripts/run-docker-local.sh](scripts/run-docker-local.sh) that runs the cypress image (see [circle.yml](circle.yml) for the current image name).
There is a script [scripts/run-docker-local.sh](scripts/run-docker-local.sh) that runs the cypress image (see [CircleCI config](.circleci/config.yml) for the current image name).
The image will start and will map the root of the repository to `/cypress` inside the image. Now you can modify the files using your favorite environment and rerun tests inside the docker environment.

View File

@@ -1,4 +1,5 @@
{
"chrome:beta": "105.0.5195.28",
"chrome:stable": "104.0.5112.101"
"chrome:stable": "104.0.5112.101",
"chrome:minimum": "64.0.3282.0"
}

3
cli/.gitignore vendored
View File

@@ -19,4 +19,5 @@ vue
vue2
react*
mount-utils
angular
angular
svelte

View File

@@ -38,7 +38,7 @@
"dayjs": "^1.10.4",
"debug": "^4.3.2",
"enquirer": "^2.3.6",
"eventemitter2": "^6.4.3",
"eventemitter2": "6.4.7",
"execa": "4.1.0",
"executable": "^4.1.1",
"extract-zip": "2.0.1",
@@ -108,7 +108,8 @@
"react",
"vue2",
"react18",
"angular"
"angular",
"svelte"
],
"bin": {
"cypress": "bin/cypress"
@@ -155,6 +156,11 @@
"import": "./angular/dist/index.js",
"require": "./angular/dist/index.js",
"types": "./angular/dist/index.d.ts"
},
"./svelte": {
"import": "./svelte/dist/cypress-svelte.esm-bundler.js",
"require": "./svelte/dist/cypress-svelte.cjs.js",
"types": "./svelte/dist/index.d.ts"
}
},
"workspaces": {

View File

@@ -13,6 +13,7 @@ const npmModulesToCopy = [
'vue',
'vue2',
'angular',
'svelte',
]
npmModulesToCopy.forEach((folder) => {

View File

@@ -1,2 +1,4 @@
// type helpers
type Nullable<T> = T | null
declare namespace Cypress {
type Nullable<T> = T | null
}

View File

@@ -1,5 +1,6 @@
/// <reference path="./cypress-npm-api.d.ts" />
/// <reference path="./cypress-eventemitter.d.ts" />
/// <reference path="./cypress-type-helpers.d.ts" />
declare namespace Cypress {
type FileContents = string | any[] | object
@@ -81,7 +82,7 @@ declare namespace Cypress {
type BrowserChannel = 'stable' | 'canary' | 'beta' | 'dev' | 'nightly' | string
type BrowserFamily = 'chromium' | 'firefox'
type BrowserFamily = 'chromium' | 'firefox' | 'webkit'
/**
* Describes a browser Cypress can control
@@ -2720,6 +2721,13 @@ declare namespace Cypress {
* @default 60000
*/
pageLoadTimeout: number
/**
* Whether Cypress will search for and replace
* obstructive JS code in .js or .html files.
*
* @see https://on.cypress.io/configuration#modifyObstructiveCode
*/
modifyObstructiveCode: boolean
/**
* Time, in milliseconds, to wait for an XHR request to go out in a [cy.wait()](https://on.cypress.io/wait) command
* @default 5000
@@ -2867,10 +2875,20 @@ declare namespace Cypress {
*/
experimentalModifyObstructiveThirdPartyCode: boolean
/**
* Generate and save commands directly to your test suite by interacting with your app as an end user would.
* Enables AST-based JS/HTML rewriting. This may fix issues caused by the existing regex-based JS/HTML replacement algorithm.
* @default false
*/
experimentalSourceRewriting: boolean
/**
* Generate and save commands directly to your test suite by interacting with your app as an end user would.
* @default false
*/
experimentalStudio: boolean
/**
* Adds support for testing in the WebKit browser engine used by Safari. See https://on.cypress.io/webkit-experiment for more information.
* @default false
*/
experimentalWebKitSupport: boolean
/**
* Number of times to retry a failed test.
* If a number is set, tests will retry in both runMode and openMode.
@@ -2963,13 +2981,6 @@ declare namespace Cypress {
* Whether Cypress was launched via 'cypress open' (interactive mode)
*/
isInteractive: boolean
/**
* Whether Cypress will search for and replace
* obstructive JS code in .js or .html files.
*
* @see https://on.cypress.io/configuration#modifyObstructiveCode
*/
modifyObstructiveCode: boolean
/**
* The platform Cypress is running on.
*/
@@ -3048,19 +3059,32 @@ declare namespace Cypress {
type PickConfigOpt<T> = T extends keyof DefineDevServerConfig ? DefineDevServerConfig[T] : any
interface AngularDevServerProjectConfig {
root: string,
sourceRoot: string,
buildOptions: Record<string, any>
}
type DevServerFn<ComponentDevServerOpts = any> = (cypressDevServerConfig: DevServerConfig, devServerConfig: ComponentDevServerOpts) => ResolvedDevServerConfig | Promise<ResolvedDevServerConfig>
type DevServerConfigOptions = {
bundler: 'webpack'
framework: 'react' | 'vue' | 'vue-cli' | 'nuxt' | 'create-react-app' | 'next' | 'angular'
framework: 'react' | 'vue' | 'vue-cli' | 'nuxt' | 'create-react-app' | 'next' | 'svelte'
webpackConfig?: PickConfigOpt<'webpackConfig'>
} | {
bundler: 'vite'
framework: 'react' | 'vue'
framework: 'react' | 'vue' | 'svelte'
viteConfig?: Omit<Exclude<PickConfigOpt<'viteConfig'>, undefined>, 'base' | 'root'>
} | {
bundler: 'webpack',
framework: 'angular',
webpackConfig?: PickConfigOpt<'webpackConfig'>,
options?: {
projectConfig: AngularDevServerProjectConfig
}
}
interface ComponentConfigOptions<ComponentDevServerOpts = any> extends Omit<CoreConfigOptions, 'baseUrl' | 'experimentalSessionAndOrigin'> {
interface ComponentConfigOptions<ComponentDevServerOpts = any> extends Omit<CoreConfigOptions, 'baseUrl' | 'experimentalSessionAndOrigin' | 'experimentalStudio'> {
devServer: DevServerFn<ComponentDevServerOpts> | DevServerConfigOptions
devServerConfig?: ComponentDevServerOpts
/**
@@ -5784,6 +5808,26 @@ declare namespace Cypress {
* cy.clock().invoke('restore')
*/
restore(): void
/**
* Change the time without invoking any timers.
*
* Default value with no argument or undefined is 0.
*
* This can be useful if you need to change the time by an hour
* while there is a setInterval registered that may otherwise run thousands
* of times.
* @see https://on.cypress.io/clock
* @example
* cy.clock()
* cy.visit('/')
* ...
* cy.clock().then(clock => {
* clock.setSystemTime(60 * 60 * 1000)
* })
* // or use this shortcut
* cy.clock().invoke('setSystemTime', 60 * 60 * 1000)
*/
setSystemTime(now?: number | Date): void
}
interface Cookie {

View File

@@ -1,6 +1,6 @@
// type tests for Cypress NPM module
// https://on.cypress.io/module-api
import cypress from 'cypress'
import cypress, { defineConfig } from 'cypress'
cypress.run // $ExpectType (options?: Partial<CypressRunOptions> | undefined) => Promise<CypressRunResult | CypressFailedRunResult>
cypress.open // $ExpectType (options?: Partial<CypressOpenOptions> | undefined) => Promise<void>
@@ -51,6 +51,10 @@ cypress.run().then(results => {
}
})
const config = defineConfig({
modifyObstructiveCode: true
})
// component options
const componentConfigNextWebpack: Cypress.ConfigOptions = {
component: {

View File

@@ -682,6 +682,24 @@ namespace CypressClockTests {
})
// restoring the clock shortcut
cy.clock().invoke('restore')
// setting system time with no argument
cy.clock().then(clock => {
clock.setSystemTime()
})
// setting system time with timestamp
cy.clock().then(clock => {
clock.setSystemTime(1000)
})
// setting system time with date object
cy.clock().then(clock => {
clock.setSystemTime(new Date(2019, 3, 2))
})
// setting system time with no argument and shortcut
cy.clock().invoke('setSystemTime')
// setting system time with timestamp and shortcut
cy.clock().invoke('setSystemTime', 1000)
// setting system time with date object and shortcut
cy.clock().invoke('setSystemTime', new Date(2019, 3, 2))
}
namespace CypressContainsTests {

View File

@@ -77,7 +77,7 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
3. If there is a new [`cypress-example-kitchensink`](https://github.com/cypress-io/cypress-example-kitchensink/releases) version, update the corresponding dependency in [`packages/example`](../packages/example) to that new version.
4. Once the `develop` branch is passing for all test projects with the new changes and the `linux-x64` binary is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64/<sha>/cypress.zip`, and the `linux-x64` cypress npm package is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64/<sha>/cypress.tgz`, publishing can proceed.
4. Once the `develop` branch is passing for all test projects with the new changes and the `linux-x64` binary is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64/develop-<sha>/cypress.zip`, and the `linux-x64` cypress npm package is present at `https://cdn.cypress.io/beta/npm/X.Y.Z/linux-x64/develop-<sha>/cypress.tgz`, publishing can proceed.
5. Install and test the pre-release version to make sure everything is working.
- Get the pre-release version that matches your system from the latest develop commit.
@@ -169,7 +169,7 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy
git pull origin develop
git log --pretty=oneline
# copy sha of the previous commit
git tag -a vX.Y.Z <sha>
git tag -a vX.Y.Z -m vX.Y.Z <sha>
git push origin vX.Y.Z
```

View File

@@ -1,6 +1,6 @@
# Testing other projects
In `develop`, `master`, and any other branch configured in [`circle.yml`](../circle.yml), the Cypress binary and npm package are built and uploaded to `cdn.cypress.io`. Then, tests are run, using a variety of real-world example repositories.
In `develop`, `master`, and any other branch configured in [the CircleCI config](../.circleci/config.yml), the Cypress binary and npm package are built and uploaded to `cdn.cypress.io`. Then, tests are run, using a variety of real-world example repositories.
Two main strategies are used to spawn these test projects:
@@ -9,7 +9,7 @@ Two main strategies are used to spawn these test projects:
## `test-binary-against-repo` jobs
A number of CI jobs in `circle.yml` clone test projects and run tests as part of `cypress-io/cypress`'s CI pipeline.
A number of CI jobs in `.circleci/config.yml` clone test projects and run tests as part of `cypress-io/cypress`'s CI pipeline.
You can find a list of test projects that do this by searching for usage of the `test-binary-against-repo` step.
@@ -19,4 +19,4 @@ One advantage to local CI is that it does not require creating commits to anothe
## `binary-system-tests`
System tests in `/system-tests/test-binary` are run against the built Cypress App in CI. For more details, see the [README](../system-tests/README.md).
System tests in `/system-tests/test-binary` are run against the built Cypress App in CI. For more details, see the [README](../system-tests/README.md).

View File

@@ -1,3 +1,46 @@
# [@cypress/angular-v1.1.0](https://github.com/cypress-io/cypress/compare/@cypress/angular-v1.0.0...@cypress/angular-v1.1.0) (2022-08-30)
### Bug Fixes
* angular 14.2 mount compilation error ([#23593](https://github.com/cypress-io/cypress/issues/23593)) ([2f337db](https://github.com/cypress-io/cypress/commit/2f337dbfa2bb212754c8fa82e3f4548a2f3a07a4))
### Features
* adding svelte component testing support ([#23553](https://github.com/cypress-io/cypress/issues/23553)) ([f6eaad4](https://github.com/cypress-io/cypress/commit/f6eaad40e1836fa9db87c60defa5ae6f390c8fd8))
# @cypress/angular-v1.0.0 (2022-08-17)
### Bug Fixes
* **angular:** set rxjs versions > 6.6.0 as dependency ([#16676](https://github.com/cypress-io/cypress/issues/16676)) ([46de81e](https://github.com/cypress-io/cypress/commit/46de81e75fd18bc37cb884e9a751106fff4d08ad))
* remove dependency causing semantic-release to fail ([#23142](https://github.com/cypress-io/cypress/issues/23142)) ([20f89bf](https://github.com/cypress-io/cypress/commit/20f89bfa32636baa8922896e719962c703129abd))
* scaffold correct config file ([#19776](https://github.com/cypress-io/cypress/issues/19776)) ([8f32960](https://github.com/cypress-io/cypress/commit/8f32960ef803f539f065d41f01fff33bfe33ed5d))
* scope config to current testing type ([#20677](https://github.com/cypress-io/cypress/issues/20677)) ([61f7cfc](https://github.com/cypress-io/cypress/commit/61f7cfc59284a2938e0a1c15d74ee75215ba5f8b))
* terminal error message for non migrated config ([#21467](https://github.com/cypress-io/cypress/issues/21467)) ([3274da7](https://github.com/cypress-io/cypress/commit/3274da7842f5ef1ddad62b1c630d0ff9120e4289))
* update scaffold template to use correct path ([#20047](https://github.com/cypress-io/cypress/issues/20047)) ([6e80359](https://github.com/cypress-io/cypress/commit/6e803597a379222cf936e5977c8314d693ee1912))
### Features
* add devServer to config file ([#18962](https://github.com/cypress-io/cypress/issues/18962)) ([2573375](https://github.com/cypress-io/cypress/commit/2573375b5b6616efd2d213a94cd55fd8e0385864))
* add template support, teardown & standalone ([#23117](https://github.com/cypress-io/cypress/issues/23117)) ([d201b37](https://github.com/cypress-io/cypress/commit/d201b37b3d6b1e37a15a8d21d853acca47bfc666))
* **angular:** angular mount ([#22858](https://github.com/cypress-io/cypress/issues/22858)) ([4131b1f](https://github.com/cypress-io/cypress/commit/4131b1fa8482ae08113bef337965baa1ac12f66c))
* Deprecate run-ct / open-ct, and update all examples to use --ct instead ([#18422](https://github.com/cypress-io/cypress/issues/18422)) ([196e8f6](https://github.com/cypress-io/cypress/commit/196e8f62cc6d27974f235945cb5700624b3dae41))
* enable Angular CT support ([#23089](https://github.com/cypress-io/cypress/issues/23089)) ([94e78eb](https://github.com/cypress-io/cypress/commit/94e78eba0430eae97529058c40611e5f24dbf140))
* ProjectLifecycleManager & general launchpad cleanup ([#19347](https://github.com/cypress-io/cypress/issues/19347)) ([4626f74](https://github.com/cypress-io/cypress/commit/4626f7481c9904fec484aa167a02e0197a3095c4))
* remove testFiles reference ([#20565](https://github.com/cypress-io/cypress/issues/20565)) ([5670344](https://github.com/cypress-io/cypress/commit/567034459089d9d53dfab5556cb9369fb335c3db))
* support specPattern, deprecate integrationFolder and componentFolder ([#19319](https://github.com/cypress-io/cypress/issues/19319)) ([792980a](https://github.com/cypress-io/cypress/commit/792980ac12746ef47b9c944ebe4c6c353a187ab2))
* support webpack-dev-server v4 ([#17918](https://github.com/cypress-io/cypress/issues/17918)) ([16e4759](https://github.com/cypress-io/cypress/commit/16e4759e0196f68c5f0525efb020211337748f94))
* swap the #__cy_root id selector to become data-cy-root for component mounting ([#20951](https://github.com/cypress-io/cypress/issues/20951)) ([0e7b555](https://github.com/cypress-io/cypress/commit/0e7b555f93fb403f431c5de4a07ae7ad6ac89ba2))
* Use .config files ([#18578](https://github.com/cypress-io/cypress/issues/18578)) ([081dd19](https://github.com/cypress-io/cypress/commit/081dd19cc6da3da229a7af9c84f62730c85a5cd6))
* use devServer instad of startDevServer ([#20092](https://github.com/cypress-io/cypress/issues/20092)) ([8a6768f](https://github.com/cypress-io/cypress/commit/8a6768fee6f46b908c5a9daf23da8b804a6c627f))
* use hoisted yarn install in binary build ([#17285](https://github.com/cypress-io/cypress/issues/17285)) ([e4f5b10](https://github.com/cypress-io/cypress/commit/e4f5b106d49d6ac0857c5fdac886f83b99558c88))
* Use plugins on config files ([#18798](https://github.com/cypress-io/cypress/issues/18798)) ([bb8251b](https://github.com/cypress-io/cypress/commit/bb8251b752ac44f1184f9160194cf12d41fc867f))
* use supportFile by testingType ([#19364](https://github.com/cypress-io/cypress/issues/19364)) ([0366d4f](https://github.com/cypress-io/cypress/commit/0366d4fa8971e5e5189c6fd6450cc3c8d72dcfe1))
# @cypress/angular-v1.0.0 (2022-08-04)

View File

@@ -1,57 +1,37 @@
> A little helper to unit test React components in the open source [Cypress.io](https://www.cypress.io/) test runner **v7.0.0+**
# @cypress/angular
**Jump to:** [Comparison](#comparison), [Blog posts](#blog-posts), [Install](#install), Examples: [basic](#basic-examples), [advanced](#advanced-examples), [full](#full-examples), [external](#external-examples), [Style options](#options), [Code coverage](#code-coverage), [Visual testing](#visual-testing), [Common problems](#common-problems), [Chat](#chat)
Mount Angular components in the open source [Cypress.io](https://www.cypress.io/) test runner **v7.0.0+**
## TLDR
- What is this? This package allows you to use [Cypress](https://www.cypress.io/) test runner to unit test your Angular components with zero effort. Here is a typical component testing, notice there is not external URL shown, since it is mounting the component directly.
![Example component test](images/dynamic.gif)
- How is this different from [Angular Testing](https://angular.io/guide/testing) or [ATL](https://testing-library.com/docs/angular-testing-library/intro/)? It is similar in functionality BUT runs the component in the real browser with full power of Cypress E2E test runner: [live GUI, full API, screen recording, CI support, cross-platform](https://www.cypress.io/features/), and [visual testing](https://on.cypress.io/visual-testing).
- Read [My Vision for Component Tests in Cypress](https://glebbahmutov.com/blog/my-vision-for-component-tests/) by Gleb Bahmutov
## Comparison
<!-- prettier-ignore-start -->
Feature | Jest / Karma / ATL | Cypress + `@cypress/angular`
--- | --- | ---
Test runs in real browser | ❌ | ✅
Supports shallow mount | ✅ | ❌
Supports full mount | ✅ | ✅
Test speed | 🏎 | [as fast as the app works in the browser](#fast-enough)
Test can use additional plugins | maybe | use any [Cypress plugin](https://on.cypress.io/plugins)
Test can interact with component | synthetic limited API | use any [Cypress command](https://on.cypress.io/api)
Test can be debugged | via terminal and Node debugger | use browser DevTools
Built-in time traveling debugger | ❌ | Cypress time traveling debugger
Re-run tests on file or test change | ✅ | ✅
Test output on CI | terminal | terminal, screenshots, videos
Tests can be run in parallel | ✅ | ✅ via [parallelization](https://on.cypress.io/parallelization)
Test against interface | if using `@testing-library/angular` | ✅ and can use `@testing-library/cypress`
Spying and stubbing methods | Jest mocks | [Sinon library](https://on.cypress.io/stubs-spies-and-clocks)
Stubbing imports | ✅ | ✅
Stubbing clock | ✅ | ✅
Code coverage | ✅ | ✅
<!-- prettier-ignore-end -->
If you are coming from Jest + ATL world, read [Test The Interface Not The Implementation](https://glebbahmutov.com/blog/test-the-interface/).
## Blog posts
- [My Vision for Component Tests in Cypress](https://glebbahmutov.com/blog/my-vision-for-component-tests/)
> **Note:** This package is bundled with the `cypress` package and should not need to be installed separately. See the [Angular Component Testing Docs](https://docs.cypress.io/guides/component-testing/quickstart-angular#Configuring-Component-Testing) for mounting Angular components. Installing and importing `mount` from `@cypress/angular` should only be used for advanced use-cases.
## Install
Requires [Node](https://nodejs.org/en/) version 12 or above.
- Requires Cypress v7.0.0 or later
- Requires [Node](https://nodejs.org/en/) version 12 or above
```sh
npm install --save-dev cypress @cypress/angular @cypress/webpack-dev-server
npm install --save-dev @cypress/angular
```
## Run
Open cypress test runner
```
npx cypress open --component
```
If you need to run test in CI
```
npx cypress run --component
```
For more information, please check the official docs for [running Cypress](https://on.cypress.io/guides/getting-started/opening-the-app#Quick-Configuration) and for [component testing](https://on.cypress.io/guides/component-testing/writing-your-first-component-test).
## API
- `mount` allows you to mount a given Angular component as a mini web application and interact with it using Cypress commands
- `mount` is the most important function, allows to mount a given Angular component as a mini web application and interact with it using Cypress commands
- `MountConfig` Configuration used to configure your test
- `createOutputSpy` factory function that creates new EventEmitter for your component and spies on it's `emit` method.
## Examples
@@ -68,87 +48,38 @@ describe('HelloWorldComponent', () => {
})
```
Look at the examples in [cypress/component](cypress/component) folder. Here is the list of examples showing various testing scenarios.
```ts
import { mount } from '@cypress/angular'
import { HelloWorldComponent } from './hello-world.component'
### Basic examples
Coming Soon...
### Advanced examples
Coming Soon...
### Full examples
Coming Soon...
### External examples
Coming Soon...
## Options
## Code coverage
In order to use code coverage you can follow the instructions from [docs](https://github.com/cypress-io/code-coverage). In most of cases you need to install 2 dependencies:
```
npm i @cypress/code-coverage babel-plugin-istanbul
yarn add @cypress/code-coverage babel-plugin-istanbul
describe('HelloWorldComponent', () => {
it('works', () => {
mount('<app-hello-world></app-hello-world>', {
declarations: [HelloWorldComponent]
})
// now use standard Cypress commands
cy.contains('Hello World!').should('be.visible')
})
})
```
If you are using [plugins/cra-v3](plugins/cra-v3) it instruments the code on the fly using `babel-plugin-istanbul` and generates report using dependency [cypress-io/code-coverage](https://github.com/cypress-io/code-coverage) (included). If you want to disable code coverage instrumentation and reporting, use `--env coverage=false` or `CYPRESS_coverage=false` or set in your `cypress.json` file
```json
{
"env": {
"coverage": false
}
}
```
## Visual testing
You can use any Cypress [Visual Testing plugin](https://on.cypress.io/plugins#visual-testing) to perform [visual testing](https://on.cypress.io/visual-testing) from the component tests. This repo has several example projects, see [visual-sudoku](examples/visual-sudoku), [visual-testing-with-percy](examples/visual-testing-with-percy), [visual-testing-with-happo](examples/visual-testing-with-happo), and [visual-testing-with-applitools](examples/visual-testing-with-applitools).
For a larger Do-It-Yourself example with an hour long list of explanation videos, see [bahmutov/sudoku](https://github.com/bahmutov/sudoku) repository. I explain how to write visual testing using open source tools in this [blog post](https://glebbahmutov.com/blog/open-source-visual-testing-of-components/), [video talk](https://www.youtube.com/watch?v=00BNExlJUU8), and [slides](https://slides.com/bahmutov/i-see-what-is-going-on).
## Common problems
Look at the examples in [cypress-component-testing-apps](https://github.com/cypress-io/cypress-component-testing-apps) repo. Here in the `angular` and `angular-standalone` folders are the two example applications showing various testing scenarios.
## Chat
## Compatibility
Come chat with us [on discord](https://discord.gg/7ZHYhZSW) in the #component-testing channel.
| @cypress/angular | cypress |
| -------------- | ------- |
| >= v1 | >= v10.5 |
## Development
See [docs/development.md](./docs/development.md)
Run `yarn build` to compile and sync packages to the `cypress` cli package.
## Debugging
## License
You can see verbose logs from this plugin by running with environment variable
[![license](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/cypress-io/cypress/blob/master/LICENSE)
```
DEBUG=@cypress/angular
```
This project is licensed under the terms of the [MIT license](/LICENSE).
Because finding and modifying Webpack settings while running this plugin is done by [find-webpack](https://github.com/bahmutov/find-webpack) module, you might want to enable its debug messages too.
```
DEBUG=@cypress/angular,find-webpack
```
## Changelog
[Changelog](./CHANGELOG.md)
## Related tools
Same feature for unit testing components from other frameworks using Cypress
- [@cypress/react](https://github.com/cypress-io/cypress/tree/develop/npm/react)
- [@cypress/vue](https://github.com/cypress-io/cypress/tree/develop/npm/vue)
- [cypress-cycle-unit-test](https://github.com/bahmutov/cypress-cycle-unit-test)
- [cypress-svelte-unit-test](https://github.com/bahmutov/cypress-svelte-unit-test)
- [@cypress/angular](https://github.com/bahmutov/@cypress/angular)
- [cypress-hyperapp-unit-test](https://github.com/bahmutov/cypress-hyperapp-unit-test)
- [cypress-angularjs-unit-test](https://github.com/bahmutov/cypress-angularjs-unit-test)
## [Changelog](./CHANGELOG.md)

View File

@@ -2,23 +2,21 @@
"name": "@cypress/angular",
"version": "0.0.0-development",
"description": "Test Angular Components using Cypress",
"private": true,
"main": "dist/index.js",
"scripts": {
"prebuild": "rimraf dist",
"build": "rollup -c rollup.config.js",
"build": "rollup -c rollup.config.mjs",
"postbuild": "node ../../scripts/sync-exported-npm-with-cli.js",
"build-prod": "yarn build",
"check-ts": "tsc --noEmit"
},
"dependencies": {},
"devDependencies": {
"@angular/common": "^14.0.6",
"@angular/core": "^14.0.6",
"@angular/platform-browser-dynamic": "^14.0.6",
"@rollup/plugin-node-resolve": "^11.1.1",
"rollup-plugin-typescript2": "^0.29.0",
"typescript": "~4.2.3",
"@angular/common": "^14.2.0",
"@angular/core": "^14.2.0",
"@angular/platform-browser-dynamic": "^14.2.0",
"@cypress/mount-utils": "0.0.0-development",
"typescript": "^4.7.4",
"zone.js": "~0.11.4"
},
"peerDependencies": {

View File

@@ -1,61 +0,0 @@
import ts from 'rollup-plugin-typescript2'
import resolve from '@rollup/plugin-node-resolve'
import pkg from './package.json'
const banner = `
/**
* ${pkg.name} v${pkg.version}
* (c) ${new Date().getFullYear()} Cypress.io
* Released under the MIT License
*/
`
function createEntry () {
const input = 'src/index.ts'
const format = 'es'
const config = {
input,
external: [
'@angular/core',
'@angular/core/testing',
'@angular/common',
'@angular/platform-browser-dynamic/testing',
'zone.js',
'zone.js/testing',
],
plugins: [
resolve(),
],
output: {
banner,
name: 'CypressAngular',
file: pkg.module,
format,
exports: 'auto',
},
}
console.log(`Building ${format}: ${config.output.file}`)
config.plugins.push(
ts({
check: true,
tsconfigOverride: {
compilerOptions: {
declaration: true,
target: 'es6', // not sure what this should be?
module: 'esnext',
},
exclude: [],
},
}),
)
return config
}
export default [
createEntry(),
]

View File

@@ -0,0 +1,14 @@
import { createEntries } from '@cypress/mount-utils/create-rollup-entry.mjs'
const config = {
external: [
'@angular/core',
'@angular/core/testing',
'@angular/common',
'@angular/platform-browser-dynamic/testing',
'zone.js',
'zone.js/testing',
],
}
export default createEntries({ formats: ['es'], input: 'src/index.ts', config })

View File

@@ -12,8 +12,8 @@ import { Component, EventEmitter, Type } from '@angular/core'
import {
ComponentFixture,
getTestBed,
TestBed,
TestModuleMetadata,
TestBed,
} from '@angular/core/testing'
import {
BrowserDynamicTestingModule,
@@ -144,12 +144,12 @@ function initTestBed<T> (
const componentFixture = createComponentFixture(component) as Type<T>
TestBed.configureTestingModule({
getTestBed().configureTestingModule({
...bootstrapModule(componentFixture, configRest),
})
if (providers != null) {
TestBed.overrideComponent(componentFixture, {
getTestBed().overrideComponent(componentFixture, {
add: {
providers,
},
@@ -172,6 +172,8 @@ function createComponentFixture<T> (
component: Type<T> | string,
): Type<T | WrapperComponent> {
if (typeof component === 'string') {
// getTestBed().overrideTemplate is available in v14+
// The static TestBed.overrideTemplate is available across versions
TestBed.overrideTemplate(WrapperComponent, component)
return WrapperComponent
@@ -192,7 +194,7 @@ function setupFixture<T> (
component: Type<T>,
config: MountConfig<T>,
): ComponentFixture<T> {
const fixture = TestBed.createComponent(component)
const fixture = getTestBed().createComponent(component)
fixture.whenStable().then(() => {
fixture.autoDetectChanges(config.autoDetectChanges ?? true)
@@ -309,6 +311,6 @@ getTestBed().initTestEnvironment(
setupHooks(() => {
// Not public, we need to call this to remove the last component from the DOM
TestBed['tearDownTestingModule']()
TestBed.resetTestingModule()
getTestBed()['tearDownTestingModule']()
getTestBed().resetTestingModule()
})

View File

@@ -41,7 +41,7 @@
"mock-fs": "5.1.1",
"shx": "0.3.3",
"snap-shot-it": "7.9.3",
"typescript": "^4.2.3"
"typescript": "^4.7.4"
},
"files": [
"dist",

View File

@@ -1,9 +1,4 @@
# Ignores TypeScript files, but keeps definitions.
*.ts
!*.d.ts
*.js.map
!src/**/__files__/**/*.js
!src/**/files/**/*.ts
*.vscode
sandbox

View File

@@ -1,3 +1,29 @@
# [@cypress/schematic-v2.1.1](https://github.com/cypress-io/cypress/compare/@cypress/schematic-v2.1.0...@cypress/schematic-v2.1.1) (2022-08-31)
### Bug Fixes
* **cypress-schematic:** suffix template files so they are not ignored ([#23645](https://github.com/cypress-io/cypress/issues/23645)) ([3fd56bc](https://github.com/cypress-io/cypress/commit/3fd56bc1f21224150569434e94b64e781b22008d))
# [@cypress/schematic-v2.1.0](https://github.com/cypress-io/cypress/compare/@cypress/schematic-v2.0.3...@cypress/schematic-v2.1.0) (2022-08-30)
### Bug Fixes
* angular 14.2 mount compilation error ([#23593](https://github.com/cypress-io/cypress/issues/23593)) ([2f337db](https://github.com/cypress-io/cypress/commit/2f337dbfa2bb212754c8fa82e3f4548a2f3a07a4))
### Features
* **cypress/schematic:** add support for component testing ([#23385](https://github.com/cypress-io/cypress/issues/23385)) ([99562af](https://github.com/cypress-io/cypress/commit/99562af65a10abb0fab211fd97b13f98e2b0f959))
# [@cypress/schematic-v2.0.3](https://github.com/cypress-io/cypress/compare/@cypress/schematic-v2.0.2...@cypress/schematic-v2.0.3) (2022-08-17)
### Bug Fixes
* cypress-schematic add exception for nguniversal ssr dev server ([#23348](https://github.com/cypress-io/cypress/issues/23348)) ([1f05ff0](https://github.com/cypress-io/cypress/commit/1f05ff0fd75db2d02a777bd497b30179b4b407f5))
# [@cypress/schematic-v2.0.2](https://github.com/cypress-io/cypress/compare/@cypress/schematic-v2.0.1...@cypress/schematic-v2.0.2) (2022-08-10)

View File

@@ -19,26 +19,36 @@
✅ Install Cypress
✅ Add npm scripts for running Cypress in `run` mode and `open` mode
✅ Add npm scripts for running Cypress e2e tests in `run` mode and `open` mode
✅ Scaffold base Cypress files and directories
✅ Provide the ability to add new e2e files easily using `ng-generate`
✅ Provide the ability to add new e2e and component specs easily using `ng-generate`
✅ Optional: prompt you to add or update the default `ng e2e` command to use Cypress.
✅ Optional: prompt you to add or update the default `ng e2e` command to use Cypress for e2e tests.
✅ Optional: prompt you to add a `ng ct` command to use Cypress component testing.
## Requirements
- Angular 12+
- Angular 13+
## Usage ⏯
Install the schematic:
### Adding E2E and Component Testing
To install the schematic via prompts:
```shell
ng add @cypress/schematic
```
To install the schematic via cli arguments (installs both e2e and component testing):
```shell
ng add @cypress/schematic --e2e --component
```
To run Cypress in `open` mode within your project:
```shell script
@@ -57,11 +67,49 @@ If you have chosen to add or update the `ng e2e` command, you can also run Cypre
ng e2e
```
To generate new e2e spec files:
If you have chosen to add Cypress component testing, you can run component tests in `open` mode using this:
```shell script
ng generate @cypress/schematic:e2e
```
ng run {project-name}:ct
```
### Generating New Cypress Spec Files
To generate a new e2e spec file:
```shell script
ng generate @cypress/schematic:spec
```
or (without cli prompt)
```shell script
ng generate @cypress/schematic:spec {name}
```
To generate a new component spec file:
```shell script
ng generate @cypress/schematic:spec --component
```
or (without cli prompt)
```shell script
ng generate @cypress/schematic:spec {component name} --component
```
To generate a new component spec file in a specific folder:
```shell script
ng generate @cypress/schematic:spec {component name} --component --path {path relative to project root}
```
To generate new component spec files alongside all component files in a project:
```shell script
ng generate @cypress/schematic:specs-ct
```
## Builder Options 🛠
@@ -109,7 +157,7 @@ We recommend setting your [Cypress Dashboard](https://on.cypress.io/features-das
Read our docs to learn more about [recording test results](https://on.cypress.io/recording-project-runs) to the [Cypress Dashboard](https://on.cypress.io/features-dashboard).
### Specifying a custom `cypress.json` config file
### Specifying a custom config file
It may be useful to have different Cypress configuration files per environment (ie. development, staging, production).
@@ -223,12 +271,22 @@ In order to prevent the application from building, add the following to the end
## Generator Options
### Specify Filename (bypassing CLI prompt)
### Specify Testing Type
In order to bypass the prompt asking for your e2e spec name, simply add a `--name=` flag like this:
The default generated spec is E2E. In order to generate a component test you can run:
```shell script
ng generate @cypress/schematic:e2e --name=login
ng generate @cypress/schematic:spec --name=button -t component
```
`-t` is an alias for `testing-type`. It accepts `e2e` or `component` as arguments. If you are using the CLI tool, a prompt will appear asking which spec type you want to generate.
### Specify Filename (bypassing CLI prompt)
In order to bypass the prompt asking for your spec name add a `--name=` flag like this:
```shell script
ng generate @cypress/schematic:spec --name=login
```
This will create a new spec file named `login.cy.ts` in the default Cypress folder location.
@@ -238,17 +296,33 @@ This will create a new spec file named `login.cy.ts` in the default Cypress fold
Add a `--project=` flag to specify the project:
```shell script
ng generate @cypress/schematic:e2e --name=login --project=sandbox
ng generate @cypress/schematic:spec --name=login --project=sandbox
```
### Specify Path
Add a `--path=` flag to specify the project:
```shell script
ng generate @cypress/schematic:e2e --name=login --path=src/app/tests
ng generate @cypress/schematic:spec --name=login --path=src/app/tests
```
This will create the e2e spec file in your specific location, creating folders as needed.
This will create a spec file in your specific location, creating folders as needed. By default, new specs are created in either `cypress/e2e` for E2E specs or `cypress/ct` for component specs.
### Generate Tests for All Components
You can scaffold component test specs alongside all your components in the default project by using:
```shell script
ng generate @cypress/schematic:specs-ct -g
```
This will identify files ending in `component.ts`. It will then create spec files alongside them - if they don't exist.
If you would like to specify a project, you can use the command:
```shell script
ng generate @cypress/schematic:specs-ct -g -p {project-name}
```
## Migrating from Protractor to Cypress?

View File

@@ -9,26 +9,26 @@
"test": "mocha -r @packages/ts/register --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json src/**/*.spec.ts"
},
"dependencies": {
"@angular-devkit/architect": "^0.1401.0",
"@angular-devkit/core": "^14.1.0",
"@angular-devkit/schematics": "^14.1.0",
"@schematics/angular": "^14.1.0",
"@angular-devkit/architect": "^0.1402.1",
"@angular-devkit/core": "^14.2.1",
"@angular-devkit/schematics": "^14.2.1",
"@schematics/angular": "^14.2.1",
"jsonc-parser": "^3.0.0",
"rxjs": "~6.6.0"
},
"devDependencies": {
"@angular-devkit/schematics-cli": "^14.1.0",
"@angular/cli": "^14.1.0",
"@angular-devkit/schematics-cli": "^14.2.1",
"@angular/cli": "^14.2.1",
"@types/chai-enzyme": "0.6.7",
"@types/mocha": "8.0.3",
"@types/node": "^18.0.6",
"chai": "4.2.0",
"mocha": "3.5.3",
"typescript": "~4.2.3"
"typescript": "^4.7.4"
},
"peerDependencies": {
"@angular/cli": ">=14.1.0",
"@angular/core": ">=14.1.0"
"@angular/cli": ">=12",
"@angular/core": ">=12"
},
"license": "MIT",
"repository": {

View File

@@ -6,6 +6,7 @@ export interface CypressBuilderOptions extends JsonObject {
browser: 'electron' | 'chrome' | 'chromium' | 'canary' | 'firefox' | 'edge' | string
devServerTarget: string
e2e: boolean
component: boolean
env: Record<string, string>
quiet: boolean
exit: boolean
@@ -20,4 +21,5 @@ export interface CypressBuilderOptions extends JsonObject {
spec: string
tsConfig: string
watch: boolean
testingType: 'e2e' | 'component'
}

View File

@@ -16,10 +16,10 @@ import { CypressBuilderOptions } from './cypressBuilderOptions'
type CypressOptions = Partial<CypressCommandLine.CypressRunOptions> &
Partial<CypressCommandLine.CypressOpenOptions>;
type StartDevServerProps = {
type CypressStartDevServerProps = {
devServerTarget: string
watch: boolean
context: any
context: BuilderContext
}
function runCypress (
@@ -75,6 +75,10 @@ function initCypress (userOptions: CypressBuilderOptions): Observable<BuilderOut
spec: '',
}
if (userOptions.component || userOptions.testingType === 'component') {
userOptions.e2e = false
}
const options: CypressOptions = {
...defaultOptions,
...userOptions,
@@ -98,20 +102,33 @@ function initCypress (userOptions: CypressBuilderOptions): Observable<BuilderOut
export function startDevServer ({
devServerTarget,
watch,
context }: StartDevServerProps): Observable<string> {
const overrides = {
watch,
}
context }: CypressStartDevServerProps): Observable<string> {
const buildTarget = targetFromTargetString(devServerTarget)
//@ts-ignore
return scheduleTargetAndForget(context, targetFromTargetString(devServerTarget), overrides).pipe(
//@ts-ignore
map((output: any) => {
if (!output.success && !watch) {
throw new Error('Could not compile application files')
return from(context.getBuilderNameForTarget(buildTarget)).pipe(
switchMap((builderName) => {
let overrides = {}
// @NOTE: Do not forward watch option if not supported by the target dev server,
// this is relevant for running Cypress against dev server target that does not support this option,
// for instance @nguniversal/builders:ssr-dev-server.
// see https://github.com/nrwl/nx/blob/f930117ed6ab13dccc40725c7e9551be081cc83d/packages/cypress/src/executors/cypress/cypress.impl.ts
if (builderName !== '@nguniversal/builders:ssr-dev-server') {
console.info(`Passing watch mode to DevServer - watch mode is ${watch}`)
overrides = {
watch,
}
}
return output.baseUrl as string
return scheduleTargetAndForget(context, targetFromTargetString(devServerTarget), overrides).pipe(
map((output: any) => {
if (!output.success && !watch) {
throw new Error('Could not compile application files')
}
return output.baseUrl as string
}),
)
}),
)
}

View File

@@ -39,6 +39,11 @@
"description": "Run end to end tests",
"default": true
},
"component": {
"type": "boolean",
"description": "Run component tests",
"default": false
},
"env": {
"type": "object",
"description": "A key-value pair of environment variables to pass to Cypress runner"
@@ -87,6 +92,13 @@
"type": "boolean",
"description": "Recompile and run tests when files change.",
"default": false
},
"testingType": {
"enum": [
"e2e",
"component"
],
"description": "Specify the type of tests to execute; either e2e or component. Defaults to e2e."
}
},
"additionalProperties": true

View File

@@ -0,0 +1,53 @@
import Fixtures, { ProjectFixtureDir } from '@tooling/system-tests'
import * as FixturesScaffold from '@tooling/system-tests/lib/dep-installer'
import execa from 'execa'
import path from 'path'
import * as fs from 'fs-extra'
const scaffoldAngularProject = async (project: string) => {
const projectPath = Fixtures.projectPath(project)
Fixtures.removeProject(project)
await Fixtures.scaffoldProject(project)
await FixturesScaffold.scaffoldProjectNodeModules(project)
await fs.remove(path.join(projectPath, 'cypress.config.ts'))
await fs.remove(path.join(projectPath, 'cypress'))
return projectPath
}
const runCommandInProject = (command: string, projectPath: string) => {
const [ex, ...args] = command.split(' ')
return execa(ex, args, { cwd: projectPath, stdio: 'inherit' })
}
// Since the schematic downloads a new version of cypress, the latest changes of
// @cypress/angular won't exist in the tmp project. To fix this, we replace the
// contents of the <project-path>/node_modules/cypress/angular with the latest
// contents of cli/angular
const copyAngularMount = async (projectPath: string) => {
await fs.copy(
path.join(__dirname, '..', '..', '..', 'cli', 'angular'),
path.join(projectPath, 'node_modules', 'cypress', 'angular'),
)
}
const cypressSchematicPackagePath = path.join(__dirname, '..')
const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-13', 'angular-14']
describe('ng add @cypress/schematic / e2e and ct', function () {
this.timeout(1000 * 60 * 4)
for (const project of ANGULAR_PROJECTS) {
it('should install ct files with option and no component specs', async () => {
const projectPath = await scaffoldAngularProject(project)
await runCommandInProject(`yarn add @cypress/schematic@file:${cypressSchematicPackagePath}`, projectPath)
await runCommandInProject('yarn ng add @cypress/schematic --e2e --component', projectPath)
await copyAngularMount(projectPath)
await runCommandInProject('yarn ng run angular:ct --watch false --spec src/app/app.component.cy.ts', projectPath)
})
}
})

View File

@@ -26,16 +26,16 @@ const cypressSchematicPackagePath = path.join(__dirname, '..')
const ANGULAR_PROJECTS: ProjectFixtureDir[] = ['angular-13', 'angular-14']
describe('cypress-schematic-e2e', function () {
describe('ng add @cypress/schematic / only e2e', function () {
this.timeout(1000 * 60 * 4)
for (const project of ANGULAR_PROJECTS) {
it('should', async () => {
it('should install e2e files by default', async () => {
const projectPath = await scaffoldAngularProject(project)
await runCommandInProject(`yarn add @cypress/schematic@file:${cypressSchematicPackagePath}`, projectPath)
await runCommandInProject('yarn ng add @cypress/schematic --e2eUpdate', projectPath)
await runCommandInProject('yarn ng e2e angular --watch false', projectPath)
await runCommandInProject('yarn ng add @cypress/schematic --e2e --component false --add-ct-specs false', projectPath)
await runCommandInProject('yarn ng e2e --watch false', projectPath)
})
}
})

View File

@@ -6,13 +6,15 @@
"factory": "./ng-add/index",
"schema": "./ng-add/schema.json"
},
"e2e": {
"description": "Create an e2e spec file",
"factory": "./ng-generate/e2e/index",
"schema": "./ng-generate/e2e/schema.json",
"aliases": [
"e2e-spec"
]
"spec": {
"description": "Create a single spec file",
"factory": "./ng-generate/cypress-test/index",
"schema": "./ng-generate/cypress-test/schema.json"
},
"specs-ct": {
"description": "Create spec files for all Angular components in a project",
"factory": "./ng-generate/cypress-ct-tests/index",
"schema": "./ng-generate/cypress-ct-tests/schema.json"
}
}
}

View File

@@ -0,0 +1,19 @@
import { defineConfig } from 'cypress'
export default defineConfig({
<% if (e2e) { %>
e2e: {
'baseUrl': '<%= baseUrl%>',
supportFile: false
},
<% } %>
<% if (component) { %>
component: {
devServer: {
framework: 'angular',
bundler: 'webpack',
},
specPattern: '**/*.cy.ts'
}
<% } %>
})

View File

@@ -1,5 +1,5 @@
// ***********************************************************
// This example support/component.ts is processed and
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and

View File

@@ -1,5 +1,5 @@
{
"extends": "<%= relativeToWorkspace %>/tsconfig.json",
"extends": "../tsconfig.json",
"include": ["**/*.ts"],
"compilerOptions": {
"sourceMap": false,

View File

@@ -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>

View File

@@ -0,0 +1,39 @@
// ***********************************************************
// This example support/component.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
import { mount } from 'cypress/angular'
// Augment the Cypress namespace to include type definitions for
// your custom command.
// Alternatively, can be defined in cypress/support/component.d.ts
// with a <reference path="./component" /> at the top of your spec.
declare global {
namespace Cypress {
interface Chainable {
mount: typeof mount
}
}
}
Cypress.Commands.add('mount', mount)
// Example use:
// cy.mount(MyComponent)

View File

@@ -31,8 +31,8 @@ describe('@cypress/schematic: ng-add', () => {
appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'application', appOptions, appTree).toPromise()
})
it('should create cypress files', async () => {
return schematicRunner.runSchematicAsync('ng-add', {}, appTree).toPromise().then((tree) => {
it('should create cypress files for e2e testing by default', async () => {
await schematicRunner.runSchematicAsync('ng-add', {}, appTree).toPromise().then((tree: UnitTestTree) => {
const files = tree.files
expect(files).to.contain('/projects/sandbox/cypress/e2e/spec.cy.ts')
@@ -43,4 +43,19 @@ describe('@cypress/schematic: ng-add', () => {
expect(files).to.contain('/projects/sandbox/cypress/fixtures/example.json')
})
})
it('should create cypress files for component testing', async () => {
await schematicRunner.runSchematicAsync('ng-add', { 'component': true }, appTree).toPromise().then((tree: UnitTestTree) => {
const files = tree.files
expect(files).to.contain('/projects/sandbox/cypress/support/component.ts')
expect(files).to.contain('/projects/sandbox/cypress/support/component-index.html')
expect(files).to.contain('/projects/sandbox/cypress/e2e/spec.cy.ts')
expect(files).to.contain('/projects/sandbox/cypress/support/e2e.ts')
expect(files).to.contain('/projects/sandbox/cypress/support/commands.ts')
expect(files).to.contain('/projects/sandbox/cypress/tsconfig.json')
expect(files).to.contain('/projects/sandbox/cypress.config.ts')
expect(files).to.contain('/projects/sandbox/cypress/fixtures/example.json')
})
})
})

View File

@@ -7,7 +7,7 @@ import {
Rule,
SchematicContext,
SchematicsException,
template,
applyTemplates,
Tree,
url,
} from '@angular-devkit/schematics'
@@ -17,20 +17,32 @@ import { concatMap, map } from 'rxjs/operators'
import { addPackageJsonDependency, NodeDependencyType } from '../utils/dependencies'
import {
getAngularJsonValue,
getAngularVersion,
getLatestNodeVersion,
NodePackage,
getDirectoriesAndCreateSpecs,
} from '../utils'
import { relative, resolve } from 'path'
import { JSONFile, JSONPath } from '../utils/jsonFile'
type HandleFilesType = {
projects: any
options: any
applyPath: string
movePath?: string
relativeToWorkspacePath: string
}
export default function (_options: any): Rule {
return (tree: Tree, _context: SchematicContext) => {
_options = { ..._options, __version__: getAngularVersion(tree) }
return chain([
updateDependencies(),
addCypressFiles(),
addCypressCoreFiles(_options),
addCypressComponentTestingFiles(_options),
addCtSpecs(_options),
addCypressTestScriptsToPackageJson(),
modifyAngularJson(_options),
])(tree, _context)
@@ -79,33 +91,79 @@ function addCypressTestScriptsToPackageJson (): Rule {
}
}
function addCypressFiles (): Rule {
function handleFiles (tree: Tree, context: SchematicContext, { projects, options, applyPath, movePath, relativeToWorkspacePath }: HandleFilesType): any {
return chain(
Object.keys(projects).map((name) => {
const project = projects[name]
const projectPath = resolve(getSystemPath(normalize(project.root)))
const workspacePath = resolve(getSystemPath(normalize('')))
const relativeToWorkspace = relative(`${projectPath}${relativeToWorkspacePath}`, workspacePath)
const baseUrl = getBaseUrl(project)
return mergeWith(
apply(url(applyPath), [
move(movePath ? `${project.root}${movePath}` : project.root),
applyTemplates({
...options,
...strings,
root: project.root ? `${project.root}/` : project.root,
baseUrl,
relativeToWorkspace,
}),
]),
)
}),
)(tree, context)
}
function addCypressCoreFiles (options: any): Rule {
return (tree: Tree, context: SchematicContext) => {
context.logger.debug('Adding cypress files')
const angularJsonValue = getAngularJsonValue(tree)
const { projects } = angularJsonValue
return chain(
return handleFiles(tree, context, {
projects,
options,
applyPath: './files-core',
relativeToWorkspacePath: `/`,
})
}
}
function addCypressComponentTestingFiles (options: any): Rule {
return (tree: Tree, context: SchematicContext) => {
if (options.component) {
context.logger.debug('Adding cypress component testing files')
const angularJsonValue = getAngularJsonValue(tree)
const { projects } = angularJsonValue
return handleFiles(tree, context, {
projects,
options,
applyPath: './files-ct',
movePath: '/cypress/support',
relativeToWorkspacePath: `/cypress`,
})
}
}
}
function addCtSpecs (options: any): Rule {
return (tree: Tree) => {
if (options.addCtSpecs) {
const angularJsonValue = getAngularJsonValue(tree)
const { projects } = angularJsonValue
Object.keys(projects).map((name) => {
const project = projects[name]
const projectPath = resolve(getSystemPath(normalize(project.root)))
const workspacePath = resolve(getSystemPath(normalize('')))
const relativeToWorkspace = relative(`${projectPath}/cypress`, workspacePath)
const baseUrl = getBaseUrl(project)
const appPath = `${project.root}/${project.sourceRoot}/${project.prefix}`
return mergeWith(
apply(url('./files'), [
move(project.root),
template({
...strings,
root: project.root ? `${project.root}/` : project.root,
baseUrl,
relativeToWorkspace,
}),
]),
)
}),
)(tree, context)
return getDirectoriesAndCreateSpecs({ tree, appPath })
})
}
}
}
@@ -129,26 +187,26 @@ function addNewCypressCommands (
runJson: JsonObject,
openJson: JsonObject,
e2eJson: JsonObject,
e2eUpdate: boolean,
e2e: boolean,
componentJson: JsonObject,
component: boolean,
) {
const projectArchitectJson = angularJsonVal['projects'][project]['architect']
projectArchitectJson['cypress-run'] = runJson
projectArchitectJson['cypress-open'] = openJson
if (e2eUpdate || !projectArchitectJson['e2e']) {
if (component) {
projectArchitectJson['ct'] = componentJson
}
if (e2e || !projectArchitectJson['e2e']) {
projectArchitectJson['e2e'] = e2eJson
}
return tree.overwrite('./angular.json', JSON.stringify(angularJsonVal, null, 2))
}
function getAngularJsonValue (tree: Tree) {
const angularJson = new JSONFile(tree, './angular.json')
return angularJson.get([]) as any
}
function modifyAngularJson (options: any): Rule {
return (tree: Tree, context: SchematicContext) => {
if (tree.exists('./angular.json')) {
@@ -195,6 +253,21 @@ function modifyAngularJson (options: any): Rule {
},
}
const componentJson = {
builder,
options: {
devServerTarget: `${project}:serve`,
watch: true,
headless: false,
testingType: 'component',
},
configurations: {
development: {
devServerTarget: `${project}:serve:development`,
},
},
}
const configFile = getCypressConfigFile(angularJsonVal, project)
if (configFile) {
@@ -202,7 +275,7 @@ function modifyAngularJson (options: any): Rule {
Object.assign(openJson.options, { configFile })
}
if (options.e2eUpdate) {
if (options.e2e) {
context.logger.debug(`Replacing e2e command with cypress-run in angular.json`)
removeE2ELinting(tree, angularJsonVal, project)
}
@@ -220,7 +293,9 @@ function modifyAngularJson (options: any): Rule {
runJson,
openJson,
e2eJson,
options.e2eUpdate,
options.e2e,
componentJson,
options.component,
)
})
} else {

View File

@@ -4,11 +4,23 @@
"title": "Cypress Install Schema",
"type": "object",
"properties": {
"e2eUpdate": {
"e2e": {
"type": "boolean",
"default": true,
"description": "When true, `ng e2e` will be added or updated to use Cypress",
"description": "When true, `ng e2e` will be added or updated to use Cypress.",
"x-prompt": "Would you like the default `ng e2e` command to use Cypress? [ Protractor to Cypress Migration Guide: https://on.cypress.io/protractor-to-cypress?cli=true ]"
},
"component": {
"type": "boolean",
"default": true,
"description": "When true, your project will set up for Cypress component testing.",
"x-prompt": "Would you like to add Cypress component testing? This will add all files needed for Cypress component testing."
},
"addCtSpecs": {
"type": "boolean",
"default": false,
"alias": "a",
"description": "When true, Cypress component tests will be added alongside `.component` files."
}
},
"required": []

View File

@@ -3,8 +3,9 @@
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'
import { join } from 'path'
import { expect } from 'chai'
import { take } from 'rxjs/operators'
describe('@cypress/schematic:e2e ng-generate', () => {
describe('ng-generate @cypress/schematic:specs-ct', () => {
const schematicRunner = new SchematicTestRunner(
'schematics',
join(__dirname, '../../collection.json'),
@@ -30,11 +31,7 @@ describe('@cypress/schematic:e2e ng-generate', () => {
appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'application', appOptions, appTree).toPromise()
})
it('should create cypress spec file', () => {
return schematicRunner.runSchematicAsync('e2e', { name: 'foo', project: 'sandbox' }, appTree).toPromise().then((tree) => {
const files = tree.files
expect(files).to.contain('/projects/sandbox/cypress/e2e/foo.cy.ts')
})
it('should create cypress component tests alongside components', async () => {
return schematicRunner.runSchematicAsync('specs-ct', { project: 'sandbox' }, appTree).pipe(take(1)).subscribe((tree: UnitTestTree) => expect(tree.files).to.contain('/projects/sandbox/app/src/app.component.cy.ts'))
})
})

View File

@@ -0,0 +1,20 @@
import { Rule, Tree, SchematicsException } from '@angular-devkit/schematics'
import { getAngularJsonValue, getDirectoriesAndCreateSpecs } from '../../utils'
import { Schema } from './schema'
export default function (options: Schema): Rule {
return (tree: Tree) => {
if (!options.project) {
throw new SchematicsException(`Invalid project name: ${options.project}`)
}
const angularJsonValue = getAngularJsonValue(tree)
const { projects } = angularJsonValue
const project = projects[options.project]
const appPath = `${project.sourceRoot}`
return getDirectoriesAndCreateSpecs({ tree, appPath })
}
}

View File

@@ -1,33 +1,23 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "cypress-schematics-e2e-spec",
"$id": "cypress-schematics-generate-ct-specs",
"title": "Cypress E2E Spec Options Schema",
"type": "object",
"properties": {
"path": {
"type": "string",
"format": "path",
"description": "The path to create the component.",
"description": "The path where the spec will be created.",
"visible": false
},
"project": {
"type": "string",
"description": "The name of the project.",
"alias": "p",
"$default": {
"$source": "projectName"
}
},
"name": {
"type": "string",
"description": "The name of the e2e test.",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What is the name of the e2e test?"
}
},
"required": [
"name"
]
"required": []
}

View File

@@ -1,10 +1,7 @@
export interface Schema {
// The name of the spec.
name: string
// The name of the project.
project?: string
// The path to create the spec.
path?: string
// The name of the project.
project?: string
}

View File

@@ -0,0 +1,40 @@
/// <reference path="../../../../../../cli/types/mocha/index.d.ts" />
import { SchematicTestRunner, UnitTestTree } from '@angular-devkit/schematics/testing'
import { join } from 'path'
import { expect } from 'chai'
describe('ng-generate @cypress/schematic:spec', () => {
const schematicRunner = new SchematicTestRunner(
'schematics',
join(__dirname, '../../collection.json'),
)
let appTree: UnitTestTree
const workspaceOptions = {
name: 'workspace',
newProjectRoot: 'projects',
version: '12.0.0',
}
const appOptions: Parameters<typeof schematicRunner['runExternalSchematicAsync']>[2] = {
name: 'sandbox',
inlineTemplate: false,
routing: false,
skipTests: false,
skipPackageJson: false,
}
beforeEach(async () => {
appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'workspace', workspaceOptions).toPromise()
appTree = await schematicRunner.runExternalSchematicAsync('@schematics/angular', 'application', appOptions, appTree).toPromise()
})
it('should create cypress e2e spec file by default', async () => {
await schematicRunner.runSchematicAsync('spec', { name: 'foo', project: 'sandbox' }, appTree).toPromise().then((tree: UnitTestTree) => expect(tree.files).to.contain('/projects/sandbox/cypress/e2e/foo.cy.ts'))
})
it('should create cypress ct spec file when testingType is component', async () => {
await schematicRunner.runSchematicAsync('spec', { name: 'foo', project: 'sandbox', component: true }, appTree).toPromise().then((tree: UnitTestTree) => expect(tree.files).to.contain('/projects/sandbox/src/app/foo.component.cy.ts'))
})
})

View File

@@ -1,13 +1,13 @@
import {
Rule, Tree, SchematicsException,
apply, url, applyTemplates, move,
chain, mergeWith,
Rule, Tree, SchematicsException, chain, mergeWith,
} from '@angular-devkit/schematics'
import { strings, normalize, virtualFs, workspaces } from '@angular-devkit/core'
import { virtualFs, workspaces } from '@angular-devkit/core'
import { Schema } from './schema'
import { createTemplate } from '../../utils'
function createSpec (tree: Tree): workspaces.WorkspaceHost {
return {
async readFile (path: string): Promise<string> {
@@ -35,6 +35,7 @@ export default function (options: Schema): Rule {
return async (tree: Tree) => {
const host = createSpec(tree)
const { workspace } = await workspaces.readWorkspace('/', host)
const testType = options.component ? 'component' : 'e2e'
let project
@@ -53,17 +54,13 @@ export default function (options: Schema): Rule {
}
if (options.path === undefined) {
options.path = `${project.root}/cypress/e2e`
options.path = testType === 'component' ? `${project.sourceRoot}/${project.prefix}` : `${project.root}/cypress/e2e`
}
const templateSource = apply(url('../files/__path__'), [
applyTemplates({
classify: strings.classify,
dasherize: strings.dasherize,
name: options.name,
}),
move(normalize(options.path as string)),
])
console.log(`Creating new ${testType} spec named: ${options.name}`)
const templatePath = testType === 'component' ? '../files/ct/__path__' : '../files/e2e/__path__'
const templateSource = createTemplate({ templatePath, options })
return chain([
mergeWith(templateSource),

View File

@@ -0,0 +1,46 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "cypress-schematics-generate-spec",
"title": "Cypress Generate Spec Options Schema",
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "Allows users to specify a custom filename.",
"visible": false
},
"path": {
"type": "string",
"format": "path",
"description": "The path where the spec will be created.",
"visible": false
},
"project": {
"type": "string",
"description": "The name of the project to create the spec in.",
"alias": "p",
"$default": {
"$source": "projectName"
}
},
"name": {
"type": "string",
"description": "The name of the spec.",
"alias": "n",
"$default": {
"$source": "argv",
"index": 0
},
"x-prompt": "What should the spec be named?"
},
"component": {
"type": "boolean",
"alias": "c",
"default": false,
"description": "When true, the spec created will be a component spec."
}
},
"required": [
"name"
]
}

View File

@@ -0,0 +1,16 @@
export interface Schema {
// Custom filename.
filename?: string
// The name of the spec.
name: string
// The path to create the spec.
path?: string
// The name of the project.
project?: string
// Create a component spec
component?: boolean
}

View File

@@ -0,0 +1,7 @@
import { <%= classify(name) %> } from './<%= fileName %>.component'
describe('<%= classify(name) %>', () => {
it('should mount', () => {
cy.mount(<%= classify(name) %>)
})
})

View File

@@ -4,7 +4,7 @@
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
* https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utility/dependencies.ts
* https://github.com/angular/angular-cli/blob/master/packages/schematics/angular/utils/dependencies.ts
*/
import { Tree } from '@angular-devkit/schematics'

View File

@@ -1,7 +1,12 @@
import { Tree } from '@angular-devkit/schematics'
import { readdirSync } from 'fs'
import { resolve } from 'path'
import { getSystemPath, normalize, strings } from '@angular-devkit/core'
import { Tree, apply, url, applyTemplates, move, Rule } from '@angular-devkit/schematics'
import { get } from 'http'
import { Schema } from '../ng-generate/cypress-test/schema'
import { getPackageJsonDependency } from './dependencies'
import { JSONFile } from './jsonFile'
export interface NodePackage {
name: string
@@ -46,3 +51,68 @@ export function getLatestNodeVersion (packageName: string): Promise<NodePackage>
return { name, version }
}
}
const ctSpecContent = ({ componentName, componentFilename }: {componentName: string, componentFilename: string}): string => {
return `import { ${componentName} } from './${componentFilename}.component'\n
describe('${componentName}', () => {
it('should mount', () => {
cy.mount(${componentName})
})
})
`
}
function generateCTSpec ({ tree, appPath, component }: { tree: Tree, appPath: string, component: any}): Rule | void {
const buffer = tree.read(`${appPath}/${component['name']}`)
const componentString = buffer?.toString()
const componentMatch = componentString?.match(/(?<=class )\S+/g)
const componentFilename = component['name'].split('.')[0]
const componentName = componentMatch ? componentMatch[0] : componentFilename
console.log(`Creating new component spec for: ${componentName}\n`)
return tree.create(`${appPath}/${componentFilename}.component.cy.ts`, ctSpecContent({ componentName, componentFilename }))
}
export function getDirectoriesAndCreateSpecs ({ appPath, tree }: { appPath: string, tree: Tree}) {
let components = []
let directories = []
const projectPath = resolve(getSystemPath(normalize('')))
const contents = readdirSync(resolve(`${projectPath}/${appPath}`), { withFileTypes: true })
if (contents) {
components = contents.filter((file) => file['name'].endsWith(`component.ts`))
directories = contents.filter((file) => file.isDirectory())
if (components) {
components.map((component) => {
return generateCTSpec({ tree, appPath, component })
})
}
if (directories) {
directories.forEach((directory: any) => {
return getDirectoriesAndCreateSpecs({ tree, appPath: `${appPath}/${directory['name']}` })
})
}
}
}
export function createTemplate ({ templatePath, options }: {templatePath: string, options: Schema}): any {
return apply(url(templatePath), [
applyTemplates({
classify: strings.classify,
dasherize: strings.dasherize,
name: options.component ? `${options.name}Component` : options.name,
fileName: options.filename || options.name,
}),
move(normalize(options.path as string)),
])
}
export function getAngularJsonValue (tree: Tree) {
const angularJson = new JSONFile(tree, './angular.json')
return angularJson.get([]) as any
}

View File

@@ -27,5 +27,5 @@
"include": [
"src/**/*"
],
"exclude": ["src/**/files/**/*", "src/**/*.spec.ts"]
"exclude": ["src/**/files-core/**/*", "src/**/files-ct/**/*", "src/**/*.spec.ts"]
}

View File

@@ -1,3 +1,10 @@
# [@cypress/mount-utils-v2.1.0](https://github.com/cypress-io/cypress/compare/@cypress/mount-utils-v2.0.1...@cypress/mount-utils-v2.1.0) (2022-08-30)
### Features
* adding svelte component testing support ([#23553](https://github.com/cypress-io/cypress/issues/23553)) ([f6eaad4](https://github.com/cypress-io/cypress/commit/f6eaad40e1836fa9db87c60defa5ae6f390c8fd8))
# [@cypress/mount-utils-v2.0.1](https://github.com/cypress-io/cypress/compare/@cypress/mount-utils-v2.0.0...@cypress/mount-utils-v2.0.1) (2022-08-11)

View File

@@ -0,0 +1,71 @@
// CommonJS to easily share across packages
import ts from 'rollup-plugin-typescript2'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import _ from 'lodash'
import { readFileSync } from 'fs'
const pkg = JSON.parse(readFileSync('./package.json'))
/** @type {(options: { formats: string[], input: string, config: {} }) => []} */
export function createEntries (options) {
const {
formats,
input,
config = {},
} = options
const banner = `
/**
* ${pkg.name} v${pkg.version}
* (c) ${new Date().getFullYear()} Cypress.io
* Released under the MIT License
*/
`
return formats.map((format) => {
const baseConfig = {
input,
plugins: [
resolve({ preferBuiltins: true }),
commonjs(),
ts({
check: format === 'es',
tsconfigOverride: {
compilerOptions: {
declaration: format === 'es',
target: 'es6',
module: format === 'cjs' ? 'es2015' : 'esnext',
},
exclude: ['tests'],
},
}),
],
output: {
banner,
name: 'CypressReact',
file: pkg.unpkg,
format,
},
}
const finalConfig = _.mergeWith({}, baseConfig, config, (objValue, srcValue) => {
if (_.isArray(objValue)) {
return objValue.concat(srcValue)
}
})
if (format === 'es') {
finalConfig.output.file = pkg.module
}
if (format === 'cjs') {
finalConfig.output.file = pkg.main
}
// eslint-disable-next-line no-console
console.log(`Building ${format}: ${finalConfig.output.file}`)
return finalConfig
})
}

View File

@@ -12,7 +12,11 @@
},
"dependencies": {},
"devDependencies": {
"typescript": "^4.2.3"
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-node-resolve": "^11.1.1",
"rollup": "^2.38.5",
"rollup-plugin-typescript2": "^0.29.0",
"typescript": "^4.7.4"
},
"files": [
"dist"

View File

@@ -1,3 +1,10 @@
# [@cypress/react-v6.2.0](https://github.com/cypress-io/cypress/compare/@cypress/react-v6.1.1...@cypress/react-v6.2.0) (2022-08-30)
### Features
* adding svelte component testing support ([#23553](https://github.com/cypress-io/cypress/issues/23553)) ([f6eaad4](https://github.com/cypress-io/cypress/commit/f6eaad40e1836fa9db87c60defa5ae6f390c8fd8))
# [@cypress/react-v6.1.1](https://github.com/cypress-io/cypress/compare/@cypress/react-v6.1.0...@cypress/react-v6.1.1) (2022-08-15)

View File

@@ -4,7 +4,7 @@
"description": "Test React components using Cypress",
"main": "dist/cypress-react.cjs.js",
"scripts": {
"build": "rimraf dist && rollup -c rollup.config.js",
"build": "rimraf dist && rollup -c rollup.config.mjs",
"postbuild": "node ../../scripts/sync-exported-npm-with-cli.js",
"build-prod": "yarn build",
"cy:open": "node ../../scripts/cypress.js open --component",
@@ -16,8 +16,6 @@
},
"devDependencies": {
"@cypress/mount-utils": "0.0.0-development",
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-node-resolve": "^11.1.1",
"@types/semver": "7.3.9",
"@vitejs/plugin-react": "1.3.1",
"axios": "0.21.2",
@@ -27,11 +25,9 @@
"react-dom": "16.8.6",
"react-router": "6.0.0-alpha.1",
"react-router-dom": "6.0.0-alpha.1",
"rollup": "^2.38.5",
"rollup-plugin-typescript2": "^0.29.0",
"semver": "^7.3.2",
"typescript": "^4.2.3",
"vite": "3.0.3",
"typescript": "^4.7.4",
"vite": "3.1.0",
"vite-plugin-require-transform": "1.0.3"
},
"peerDependencies": {

View File

@@ -1,74 +0,0 @@
// CommonJS to easily share across packages
const ts = require('rollup-plugin-typescript2')
const { default: resolve } = require('@rollup/plugin-node-resolve')
const commonjs = require('@rollup/plugin-commonjs')
const pkg = require('./package.json')
const banner = `
/**
* ${pkg.name} v${pkg.version}
* (c) ${new Date().getFullYear()} Cypress.io
* Released under the MIT License
*/
`
function createEntry (options) {
const {
format,
input,
} = options
const config = {
input,
external: [
'react',
'react-dom',
'react-dom/client',
],
plugins: [
resolve(),
commonjs(),
ts({
check: format === 'es',
tsconfigOverride: {
compilerOptions: {
declaration: format === 'es',
target: 'es5',
module: format === 'cjs' ? 'es2015' : 'esnext',
},
exclude: ['tests'],
},
}),
],
output: {
banner,
name: 'CypressReact',
file: pkg.unpkg,
format,
globals: {
react: 'React',
'react-dom': 'ReactDOM',
'react-dom/client': 'ReactDOM/client',
},
},
}
if (format === 'es') {
config.output.file = pkg.module
}
if (format === 'cjs') {
config.output.file = pkg.main
}
// eslint-disable-next-line no-console
console.log(`Building ${format}: ${config.output.file}`)
return config
}
module.exports = [
createEntry({ format: 'es', input: 'src/index.ts' }),
createEntry({ format: 'cjs', input: 'src/index.ts' }),
]

View File

@@ -0,0 +1,18 @@
import { createEntries } from '@cypress/mount-utils/create-rollup-entry.mjs'
const config = {
external: [
'react',
'react-dom',
'react-dom/client',
],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM',
'react-dom/client': 'ReactDOM/client',
},
},
}
export default createEntries({ formats: ['es', 'cjs'], input: 'src/index.ts', config })

View File

@@ -1,3 +1,10 @@
# [@cypress/react18-v1.1.0](https://github.com/cypress-io/cypress/compare/@cypress/react18-v1.0.1...@cypress/react18-v1.1.0) (2022-08-30)
### Features
* adding svelte component testing support ([#23553](https://github.com/cypress-io/cypress/issues/23553)) ([f6eaad4](https://github.com/cypress-io/cypress/commit/f6eaad40e1836fa9db87c60defa5ae6f390c8fd8))
# [@cypress/react18-v1.0.1](https://github.com/cypress-io/cypress/compare/@cypress/react18-v1.0.0...@cypress/react18-v1.0.1) (2022-08-15)

View File

@@ -4,7 +4,7 @@
"description": "Test React components using Cypress",
"main": "dist/cypress-react.cjs.js",
"scripts": {
"build": "rimraf dist && rollup -c rollup.config.js",
"build": "rimraf dist && rollup -c rollup.config.mjs",
"postbuild": "node ../../scripts/sync-exported-npm-with-cli.js",
"build-prod": "yarn build",
"watch": "yarn build --watch --watch.exclude ./dist/**/*"
@@ -20,7 +20,7 @@
"react-dom": "^16",
"rollup": "^2.38.5",
"rollup-plugin-typescript2": "^0.29.0",
"typescript": "^4.2.3"
"typescript": "^4.7.4"
},
"peerDependencies": {
"@types/react": "^18",

View File

@@ -1,3 +0,0 @@
import * as RollupConfig from '../react/rollup.config'
export default RollupConfig

View File

@@ -0,0 +1,3 @@
import rollupConfig from '@cypress/react/rollup.config.mjs'
export default rollupConfig

View File

@@ -11,12 +11,14 @@ import type {
UnmountArgs,
} from '@cypress/react'
let root: any
let root: ReactDOM.Root | null
const cleanup = () => {
if (root) {
root.unmount()
root = null
return true
}
@@ -27,7 +29,9 @@ export function mount (jsx: React.ReactNode, options: MountOptions = {}, rerende
const internalOptions: InternalMountOptions = {
reactDom: ReactDOM,
render: (reactComponent: ReturnType<typeof React.createElement>, el: HTMLElement) => {
root = ReactDOM.createRoot(el)
if (!root) {
root = ReactDOM.createRoot(el)
}
return root.render(reactComponent)
},

17
npm/svelte/.eslintrc Normal file
View File

@@ -0,0 +1,17 @@
{
"plugins": [
"cypress"
],
"extends": [
"plugin:@cypress/dev/tests"
],
"env": {
"cypress/globals": true
},
"rules": {
"mocha/no-global-tests": "off",
"no-unused-vars": "off",
"no-console": "off",
"@typescript-eslint/no-unused-vars": "off"
}
}

3
npm/svelte/.npmrc Normal file
View File

@@ -0,0 +1,3 @@
save-exact=true
progress=false
package-lock=true

7
npm/svelte/.releaserc.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
...require('../../.releaserc.base'),
branches: [
// this one releases v3 on master on the latest channel
'master',
],
}

6
npm/svelte/CHANGELOG.md Normal file
View File

@@ -0,0 +1,6 @@
# @cypress/svelte-v1.0.0 (2022-08-30)
### Features
* adding svelte component testing support ([#23553](https://github.com/cypress-io/cypress/issues/23553)) ([f6eaad4](https://github.com/cypress-io/cypress/commit/f6eaad40e1836fa9db87c60defa5ae6f390c8fd8))

83
npm/svelte/README.md Normal file
View File

@@ -0,0 +1,83 @@
# @cypress/svelte
Mount Svelte components in the open source [Cypress.io](https://www.cypress.io/) test runner **v10.7.0+**
> **Note:** This package is bundled with the `cypress` package and should not need to be installed separately. See the [Svelte Component Testing Docs](https://docs.cypress.io/guides/component-testing/quickstart-svelte#Configuring-Component-Testing) for mounting Svelte components. Installing and importing `mount` from `@cypress/svelte` should only be used for advanced use-cases.
## Install
- Requires Svelte >= 3
- Requires Cypress v10.7.0 or later
- Requires [Node](https://nodejs.org/en/) version 12 or above
```sh
npm install --save-dev @cypress/svelte
```
## Run
Open cypress test runner
```
npx cypress open --component
```
If you need to run test in CI
```
npx cypress run --component
```
For more information, please check the official docs for [running Cypress](https://on.cypress.io/guides/getting-started/opening-the-app#Quick-Configuration) and for [component testing](https://on.cypress.io/guides/component-testing/writing-your-first-component-test).
## Example
```js
import { mount } from '@cypress/svelte'
import HelloWorld from './HelloWorld.svelte'
describe('HelloWorld component', () => {
it('works', () => {
mount(HelloWorld)
// now use standard Cypress commands
cy.contains('Hello World!').should('be.visible')
})
})
```
## Options
In most cases, the component already imports its own styles, thus it looks "right" during the test. If you need another CSS, the simplest way is to import it from the spec file:
```js
// src/HelloWorld.svelte
import './styles/main.css'
import HelloWorld from './HelloWorld.svelte'
it('looks right', () => {
// styles are applied
mount(HelloWorld)
})
```
> Note: Global styles can be imported in your component support file, allowing the styles to apply to all mounted components.
## Compatibility
| @cypress/svelte | cypress |
| -------------- | ------- |
| >= v1 | >= v10 |
## Development
Run `yarn build` to compile and sync packages to the `cypress` cli package.
Run `yarn cy:open` to open Cypress component testing against real-world examples.
Run `yarn test` to execute headless Cypress tests.
## License
[![license](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/cypress-io/cypress/blob/master/LICENSE)
This project is licensed under the terms of the [MIT license](/LICENSE).
## [Changelog](./CHANGELOG.md)

43
npm/svelte/package.json Normal file
View File

@@ -0,0 +1,43 @@
{
"name": "@cypress/svelte",
"version": "0.0.0-development",
"description": "Browser-based Component Testing for Svelte.js with Cypress.io 🧡",
"main": "dist/cypress-svelte.cjs.js",
"scripts": {
"prebuild": "rimraf dist",
"build": "rollup -c rollup.config.mjs",
"postbuild": "node ../../scripts/sync-exported-npm-with-cli.js",
"build-prod": "yarn build",
"check-ts": "tsc --noEmit"
},
"devDependencies": {
"@cypress/mount-utils": "0.0.0-development",
"svelte": "^3.49.0",
"typescript": "^4.7.4"
},
"peerDependencies": {
"cypress": ">=10.6.0",
"svelte": ">=3.0.0"
},
"files": [
"dist/**/*"
],
"types": "dist/index.d.ts",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/cypress-io/cypress.git"
},
"homepage": "https://github.com/cypress-io/cypress/blob/master/npm/svelte/#readme",
"bugs": "https://github.com/cypress-io/cypress/issues/new?assignees=&labels=npm%3A%20%40cypress%2Fsvelte&template=1-bug-report.md&title=",
"keywords": [
"cypress",
"svelte",
"testing",
"component testing"
],
"module": "dist/cypress-svelte.esm-bundler.js",
"publishConfig": {
"access": "public"
}
}

View File

@@ -0,0 +1,3 @@
import { createEntries } from '@cypress/mount-utils/create-rollup-entry.mjs'
export default createEntries({ formats: ['es', 'cjs'], input: 'src/index.ts' })

1
npm/svelte/src/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './mount'

100
npm/svelte/src/mount.ts Normal file
View File

@@ -0,0 +1,100 @@
import {
injectStylesBeforeElement,
StyleOptions,
getContainerEl,
setupHooks,
} from '@cypress/mount-utils'
import type { ComponentConstructorOptions, ComponentProps, SvelteComponent } from 'svelte'
const DEFAULT_COMP_NAME = 'unknown'
type SvelteConstructor<T> = new (...args: any[]) => T;
type SvelteComponentOptions<T extends SvelteComponent> = Omit<
ComponentConstructorOptions<ComponentProps<T>>,
'hydrate' | 'target' | '$$inline'
>;
export interface MountOptions<T extends SvelteComponent>
extends SvelteComponentOptions<T>,
Partial<StyleOptions> {
log?: boolean
}
export interface MountReturn<T extends SvelteComponent> {
component: T
}
let componentInstance: SvelteComponent | undefined
const cleanup = () => {
componentInstance?.$destroy()
}
// Extract the component name from the object passed to mount
const getComponentDisplayName = <T extends SvelteComponent>(Component: SvelteConstructor<T>): string => {
if (Component.name) {
const [_, match] = /Proxy\<(\w+)\>/.exec(Component.name) || []
return match || Component.name
}
return DEFAULT_COMP_NAME
}
/**
* Mounts a Svelte component inside the Cypress browser
*
* @param {SvelteConstructor<T>} Component Svelte component being mounted
* @param {MountReturn<T extends SvelteComponent>} options options to customize the component being mounted
* @returns Cypress.Chainable<MountReturn>
*
* @example
* import Counter from './Counter.svelte'
* import { mount } from 'cypress/svelte'
*
* it('should render', () => {
* mount(Counter, { props: { count: 42 } })
* cy.get('button').contains(42)
* })
*/
export function mount<T extends SvelteComponent> (
Component: SvelteConstructor<T>,
options: MountOptions<T> = {},
): Cypress.Chainable<MountReturn<T>> {
return cy.then(() => {
const target = getContainerEl()
injectStylesBeforeElement(options, document, target)
const ComponentConstructor = ((Component as any).default || Component) as SvelteConstructor<T>
componentInstance = new ComponentConstructor({
target,
...options,
})
// by waiting, we are delaying test execution for the next tick of event loop
// and letting hooks and component lifecycle methods to execute mount
return cy.wait(0, { log: false }).then(() => {
if (options.log !== false) {
const mountMessage = `<${getComponentDisplayName(Component)} ... />`
Cypress.log({
name: 'mount',
message: [mountMessage],
}).snapshot('mounted').end()
}
})
.wrap({ component: componentInstance as T }, { log: false })
})
}
// Side effects from "import { mount } from '@cypress/<my-framework>'" are annoying, we should avoid doing this
// by creating an explicit function/import that the user can register in their 'component.js' support file,
// such as:
// import 'cypress/<my-framework>/support'
// or
// import { registerCT } from 'cypress/<my-framework>'
// registerCT()
// Note: This would be a breaking change
setupHooks(cleanup)

22
npm/svelte/tsconfig.json Normal file
View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"skipLibCheck": true,
"lib": [
"es2015",
"dom"
],
"allowJs": true,
"jsx": "preserve",
"outDir": "dist",
"strict": true,
"baseUrl": "./",
"types": [
"cypress"
],
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
},
"include": ["src"],
}

View File

@@ -26,8 +26,8 @@
"dedent": "^0.7.0",
"mocha": "^9.2.2",
"sinon": "^13.0.1",
"ts-node": "^10.2.1",
"vite": "3.0.3",
"ts-node": "^10.9.1",
"vite": "3.1.0",
"vite-plugin-inspect": "0.4.3"
},
"files": [

View File

@@ -1,3 +1,10 @@
# [@cypress/vue-v4.2.0](https://github.com/cypress-io/cypress/compare/@cypress/vue-v4.1.0...@cypress/vue-v4.2.0) (2022-08-30)
### Features
* adding svelte component testing support ([#23553](https://github.com/cypress-io/cypress/issues/23553)) ([f6eaad4](https://github.com/cypress-io/cypress/commit/f6eaad40e1836fa9db87c60defa5ae6f390c8fd8))
# [@cypress/vue-v4.1.0](https://github.com/cypress-io/cypress/compare/@cypress/vue-v4.0.0...@cypress/vue-v4.1.0) (2022-08-11)

View File

@@ -7,7 +7,7 @@
"build-prod": "yarn build",
"cy:open": "node ../../scripts/cypress.js open --component --project ${PWD}",
"cy:run": "node ../../scripts/cypress.js run --component --project ${PWD}",
"build": "rimraf dist && rollup -c rollup.config.js",
"build": "rimraf dist && rollup -c rollup.config.mjs",
"postbuild": "node --require @packages/ts/register ./inline-types.ts && node ../../scripts/sync-exported-npm-with-cli.js",
"typecheck": "yarn tsd && vue-tsc --noEmit",
"test": "yarn cy:run",
@@ -16,8 +16,6 @@
},
"devDependencies": {
"@cypress/mount-utils": "0.0.0-development",
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-node-resolve": "^11.1.1",
"@vitejs/plugin-vue": "2.3.1",
"@vue/compiler-sfc": "3.2.31",
"@vue/test-utils": "2.0.2",
@@ -25,12 +23,9 @@
"cypress": "0.0.0-development",
"debug": "^4.3.2",
"globby": "^11.0.1",
"rollup": "^2.38.5",
"rollup-plugin-istanbul": "2.0.1",
"rollup-plugin-typescript2": "^0.29.0",
"tailwindcss": "1.1.4",
"typescript": "^4.2.3",
"vite": "3.0.3",
"typescript": "^4.7.4",
"vite": "3.1.0",
"vue": "3.2.31",
"vue-i18n": "9.0.0-rc.6",
"vue-router": "^4.0.0",

View File

@@ -1,75 +0,0 @@
import ts from 'rollup-plugin-typescript2'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import pkg from './package.json'
const banner = `
/**
* ${pkg.name} v${pkg.version}
* (c) ${new Date().getFullYear()} Cypress.io
* Released under the MIT License
*/
`
function createEntry (options) {
const {
format,
input,
} = options
const config = {
input,
external: [
'vue',
],
plugins: [
resolve({ preferBuiltins: true }), commonjs(),
],
output: {
banner,
name: 'CypressVue',
file: pkg.unpkg,
format,
globals: {
vue: 'Vue',
},
exports: 'auto',
},
}
if (input === 'src/index.ts') {
if (format === 'es') {
config.output.file = pkg.module
}
if (format === 'cjs') {
config.output.file = pkg.main
}
} else {
config.output.file = input.replace(/^src\//, 'dist/')
}
console.log(`Building ${format}: ${config.output.file}`)
config.plugins.push(
ts({
check: false,
tsconfigOverride: {
compilerOptions: {
declaration: format === 'es',
noEmit: false,
module: format === 'cjs' ? 'es2015' : 'esnext',
},
exclude: ['cypress/component'],
},
}),
)
return config
}
export default [
createEntry({ format: 'es', input: 'src/index.ts' }),
createEntry({ format: 'cjs', input: 'src/index.ts' }),
]

14
npm/vue/rollup.config.mjs Normal file
View File

@@ -0,0 +1,14 @@
import { createEntries } from '@cypress/mount-utils/create-rollup-entry.mjs'
const config = {
external: [
'vue',
],
output: {
globals: {
vue: 'Vue',
},
},
}
export default createEntries({ formats: ['es', 'cjs'], input: 'src/index.ts', config })

View File

@@ -1,3 +1,10 @@
# [@cypress/vue2-v1.1.0](https://github.com/cypress-io/cypress/compare/@cypress/vue2-v1.0.2...@cypress/vue2-v1.1.0) (2022-08-30)
### Features
* adding svelte component testing support ([#23553](https://github.com/cypress-io/cypress/issues/23553)) ([f6eaad4](https://github.com/cypress-io/cypress/commit/f6eaad40e1836fa9db87c60defa5ae6f390c8fd8))
# [@cypress/vue2-v1.0.2](https://github.com/cypress-io/cypress/compare/@cypress/vue2-v1.0.1...@cypress/vue2-v1.0.2) (2022-08-11)

View File

@@ -5,7 +5,7 @@
"main": "dist/cypress-vue2.cjs.js",
"scripts": {
"typecheck": "tsc --noEmit",
"build": "rimraf dist && yarn rollup -c rollup.config.js",
"build": "rimraf dist && yarn rollup -c rollup.config.mjs",
"postbuild": "node ../../scripts/sync-exported-npm-with-cli.js",
"build-prod": "yarn build",
"test": "echo \"Tests for @cypress/vue2 are run from system-tests\"",
@@ -17,13 +17,10 @@
},
"devDependencies": {
"@cypress/mount-utils": "0.0.0-development",
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.1.1",
"@rollup/plugin-replace": "^2.3.1",
"rollup-plugin-typescript2": "^0.29.0",
"tslib": "^2.1.0",
"typescript": "^4.2.3",
"typescript": "^4.7.4",
"vue": "2.6.12"
},
"peerDependencies": {

View File

@@ -1,93 +0,0 @@
import ts from 'rollup-plugin-typescript2'
import resolve from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import replace from '@rollup/plugin-replace'
import pkg from './package.json'
const banner = `
/**
* ${pkg.name} v${pkg.version}
* (c) ${new Date().getFullYear()} Cypress.io
* Released under the MIT License
*/
`
function createEntry (options) {
const {
format,
input,
isBrowser,
} = options
const config = {
input,
external: [
'vue',
],
plugins: [
resolve({ preferBuiltins: true }),
commonjs(),
json(),
/**
* Vue 2 core tries to load require.resolve(`./package.json`) in the browser for the
* sole purpose of throwing an error about Vue Loader.
* Just truncate this for now for simplicity.
*/
replace({
'vueVersion && vueVersion !== packageVersion': JSON.stringify(false),
}),
],
output: {
banner,
name: 'CypressVue2',
file: pkg.unpkg,
format,
globals: {
vue: 'Vue',
},
exports: 'auto',
},
}
if (input === 'src/index.ts') {
if (format === 'es') {
config.output.file = pkg.module
if (isBrowser) {
config.output.file = pkg.unpkg
}
}
if (format === 'cjs') {
config.output.file = pkg.main
}
} else {
config.output.file = input.replace(/^src\//, 'dist/')
}
console.log(`Building ${format}: ${config.output.file}`)
config.plugins.push(
ts({
check: format === 'es' && isBrowser,
tsconfigOverride: {
compilerOptions: {
declaration: format === 'es',
target: 'es5', // not sure what this should be?
module: format === 'cjs' ? 'es2015' : 'esnext',
},
exclude: ['tests'],
},
}),
)
return config
}
export default [
createEntry({ format: 'es', input: 'src/index.ts', isBrowser: false }),
createEntry({ format: 'es', input: 'src/index.ts', isBrowser: true }),
createEntry({ format: 'iife', input: 'src/index.ts', isBrowser: true }),
createEntry({ format: 'cjs', input: 'src/index.ts', isBrowser: false }),
]

View File

@@ -0,0 +1,28 @@
import { createEntries } from '@cypress/mount-utils/create-rollup-entry.mjs'
import json from '@rollup/plugin-json'
import replace from '@rollup/plugin-replace'
const config = {
external: [
'vue',
],
plugins: [
json(),
/**
* Vue 2 core tries to load require.resolve(`./package.json`) in the browser for the
* sole purpose of throwing an error about Vue Loader.
* Just truncate this for now for simplicity.
*/
replace({
'vueVersion && vueVersion !== packageVersion': JSON.stringify(false),
preventAssignment: false,
}),
],
output: {
globals: {
vue: 'Vue',
},
},
}
export default createEntries({ formats: ['es', 'cjs'], input: 'src/index.ts', config })

View File

@@ -19,7 +19,7 @@
"coffee-loader": "^0.9.0",
"coffeescript": "^1.12.7",
"pnp-webpack-plugin": "^1.7.0",
"ts-loader": "^8.0.2",
"ts-loader": "8.4.0",
"tsconfig-package": "npm:tsconfig@^7.0.0",
"tsconfig-paths-webpack-plugin": "^3.3.0",
"webpack": "^4.44.2"
@@ -41,7 +41,7 @@
"graphql": "14.0.0",
"mocha": "^8.1.1",
"react": "^16.13.1",
"typescript": "^4.2.3"
"typescript": "^4.7.4"
},
"peerDependencies": {
"@cypress/webpack-preprocessor": "^5.4.4"

View File

@@ -1,3 +1,15 @@
# [@cypress/webpack-dev-server-v2.3.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-dev-server-v2.2.0...@cypress/webpack-dev-server-v2.3.0) (2022-08-30)
### Bug Fixes
* **webpack-dev-server:** add custom project config to handler ([a07a2a1](https://github.com/cypress-io/cypress/commit/a07a2a118d7b62b90e790ef475c86959ae894b3b))
### Features
* adding svelte component testing support ([#23553](https://github.com/cypress-io/cypress/issues/23553)) ([f6eaad4](https://github.com/cypress-io/cypress/commit/f6eaad40e1836fa9db87c60defa5ae6f390c8fd8))
# [@cypress/webpack-dev-server-v2.2.0](https://github.com/cypress-io/cypress/compare/@cypress/webpack-dev-server-v2.1.0...@cypress/webpack-dev-server-v2.2.0) (2022-08-15)

View File

@@ -37,7 +37,7 @@
"proxyquire": "2.1.3",
"sinon": "^13.0.1",
"snap-shot-it": "^7.9.6",
"ts-node": "^10.2.1",
"ts-node": "^10.9.1",
"webpack": "npm:webpack@^5",
"webpack-4": "npm:webpack@^4",
"webpack-dev-server-3": "npm:webpack-dev-server@^3"

View File

@@ -16,17 +16,24 @@ import { angularHandler } from './helpers/angularHandler'
const debug = debugLib('cypress:webpack-dev-server:devServer')
export type Frameworks = Extract<Cypress.DevServerConfigOptions, { bundler: 'webpack' }>['framework']
type FrameworkConfig = {
framework?: Exclude<Frameworks, 'angular'>
} | {
framework: 'angular'
options?: {
projectConfig: Cypress.AngularDevServerProjectConfig
}
}
export type WebpackDevServerConfig = {
specs: Cypress.Spec[]
cypressConfig: Cypress.PluginConfigOptions
devServerEvents: NodeJS.EventEmitter
onConfigNotFound?: (devServer: 'webpack', cwd: string, lookedIn: string[]) => void
} & {
framework?: typeof ALL_FRAMEWORKS[number] // Add frameworks here as we implement
webpackConfig?: unknown // Derived from the user's webpack
}
export const ALL_FRAMEWORKS = ['create-react-app', 'nuxt', 'react', 'vue-cli', 'next', 'vue', 'angular'] as const
} & FrameworkConfig
/**
* @internal
@@ -121,11 +128,12 @@ async function getPreset (devServerConfig: WebpackDevServerConfig): Promise<Opti
case 'react':
case 'vue':
case 'svelte':
case undefined:
return { sourceWebpackModulesResult: sourceDefaultWebpackDependencies(devServerConfig) }
default:
throw new Error(`Unexpected framework ${devServerConfig.framework}, expected one of ${ALL_FRAMEWORKS.join(', ')}`)
throw new Error(`Unexpected framework ${(devServerConfig as any).framework}, please visit https://docs.cypress.io/guides/component-testing/component-framework-configuration to see a list of supported frameworks`)
}
}

View File

@@ -5,19 +5,22 @@ import { pathToFileURL } from 'url'
import type { PresetHandlerResult, WebpackDevServerConfig } from '../devServer'
import { sourceDefaultWebpackDependencies } from './sourceRelativeWebpackModules'
export type BuildOptions = Record<string, any>
export type AngularWebpackDevServerConfig = Extract<WebpackDevServerConfig, {framework: 'angular'}>
type Configurations = {
configurations?: {
[configuration: string]: BuildOptions
}
}
export type AngularJsonProjectConfig = {
projectType: string
root: string
sourceRoot: string
architect: {
build: {
options: { [key: string]: any } & { polyfills?: string }
configurations?: {
[configuration: string]: {
[key: string]: any
}
}
}
build: { options: BuildOptions } & Configurations
}
}
@@ -30,21 +33,7 @@ type AngularJson = {
const dynamicImport = new Function('specifier', 'return import(specifier)')
export async function angularHandler (devServerConfig: WebpackDevServerConfig): Promise<PresetHandlerResult> {
const webpackConfig = await getAngularCliWebpackConfig(devServerConfig)
return { frameworkConfig: webpackConfig, sourceWebpackModulesResult: sourceDefaultWebpackDependencies(devServerConfig) }
}
async function getAngularCliWebpackConfig (devServerConfig: WebpackDevServerConfig) {
const { projectRoot } = devServerConfig.cypressConfig
const {
generateBrowserWebpackConfigFromContext,
getCommonConfig,
getStylesConfig,
} = await getAngularCliModules(projectRoot)
export async function getProjectConfig (projectRoot: string): Promise<Cypress.AngularDevServerProjectConfig> {
const angularJson = await getAngularJson(projectRoot)
let { defaultProject } = angularJson
@@ -53,30 +42,26 @@ async function getAngularCliWebpackConfig (devServerConfig: WebpackDevServerConf
defaultProject = Object.keys(angularJson.projects).find((name) => angularJson.projects[name].projectType === 'application')
if (!defaultProject) {
throw new Error('Could not find a project with projectType "application" in "angular.json"')
throw new Error('Could not find a project with projectType "application" in "angular.json". Visit https://docs.cypress.io/guides/references/configuration#Options-API to see how to pass in a custom project configuration')
}
}
const defaultProjectConfig = angularJson.projects[defaultProject]
const tsConfig = await generateTsConfig(devServerConfig, defaultProjectConfig)
const { architect, root, sourceRoot } = defaultProjectConfig
const { build } = architect
const buildOptions = getAngularBuildOptions(defaultProjectConfig, tsConfig)
const context = createFakeContext(projectRoot, defaultProject, defaultProjectConfig)
const { config } = await generateBrowserWebpackConfigFromContext(
buildOptions,
context,
(wco: any) => [getCommonConfig(wco), getStylesConfig(wco)],
)
delete config.entry.main
return config
return {
root,
sourceRoot,
buildOptions: {
...build.options,
...build.configurations?.development || {},
},
}
}
export function getAngularBuildOptions (projectConfig: AngularJsonProjectConfig, tsConfig: string) {
export function getAngularBuildOptions (buildOptions: BuildOptions, tsConfig: string) {
// Default options are derived from the @angular-devkit/build-angular browser builder, with some options from
// the serve builder thrown in for development.
// see: https://github.com/angular/angular-cli/blob/main/packages/angular_devkit/build_angular/src/builders/browser/schema.json
@@ -115,8 +100,7 @@ export function getAngularBuildOptions (projectConfig: AngularJsonProjectConfig,
extractLicenses: false,
sourceMap: true,
namedChunks: true,
...projectConfig.architect.build.options,
...projectConfig.architect.build.configurations?.development || {},
...buildOptions,
tsConfig,
aot: false,
outputHashing: 'none',
@@ -124,7 +108,7 @@ export function getAngularBuildOptions (projectConfig: AngularJsonProjectConfig,
}
}
export async function generateTsConfig (devServerConfig: WebpackDevServerConfig, projectConfig: AngularJsonProjectConfig): Promise<string> {
export async function generateTsConfig (devServerConfig: AngularWebpackDevServerConfig, buildOptions: BuildOptions): Promise<string> {
const { cypressConfig } = devServerConfig
const { projectRoot } = cypressConfig
@@ -138,8 +122,8 @@ export async function generateTsConfig (devServerConfig: WebpackDevServerConfig,
includePaths.push(toPosix(cypressConfig.supportFile))
}
if (projectConfig.architect.build.options.polyfills) {
const polyfills = getProjectFilePath(projectConfig.architect.build.options.polyfills)
if (buildOptions.polyfills) {
const polyfills = getProjectFilePath(buildOptions.polyfills)
includePaths.push(polyfills)
}
@@ -149,7 +133,7 @@ export async function generateTsConfig (devServerConfig: WebpackDevServerConfig,
includePaths.push(cypressTypes)
const tsConfigContent = JSON.stringify({
extends: getProjectFilePath('tsconfig.json'),
extends: getProjectFilePath(buildOptions.tsConfig ?? 'tsconfig.json'),
compilerOptions: {
outDir: getProjectFilePath('out-tsc/cy'),
allowSyntheticDefaultImports: true,
@@ -215,14 +199,14 @@ export async function getAngularJson (projectRoot: string): Promise<AngularJson>
return JSON.parse(angularJson)
}
function createFakeContext (projectRoot: string, defaultProject: string, defaultProjectConfig: any) {
function createFakeContext (projectRoot: string, defaultProjectConfig: Cypress.AngularDevServerProjectConfig) {
const logger = {
createChild: () => ({}),
}
const context = {
target: {
project: defaultProject,
project: 'angular',
},
workspaceRoot: projectRoot,
getProjectMetadata: () => {
@@ -239,3 +223,38 @@ function createFakeContext (projectRoot: string, defaultProject: string, default
}
export const toPosix = (filePath: string) => filePath.split(path.sep).join(path.posix.sep)
async function getAngularCliWebpackConfig (devServerConfig: AngularWebpackDevServerConfig) {
const { projectRoot } = devServerConfig.cypressConfig
const {
generateBrowserWebpackConfigFromContext,
getCommonConfig,
getStylesConfig,
} = await getAngularCliModules(projectRoot)
// normalize
const projectConfig = devServerConfig.options?.projectConfig || await getProjectConfig(projectRoot)
const tsConfig = await generateTsConfig(devServerConfig, projectConfig.buildOptions)
const buildOptions = getAngularBuildOptions(projectConfig.buildOptions, tsConfig)
const context = createFakeContext(projectRoot, projectConfig)
const { config } = await generateBrowserWebpackConfigFromContext(
buildOptions,
context,
(wco: any) => [getCommonConfig(wco), getStylesConfig(wco)],
)
delete config.entry.main
return config
}
export async function angularHandler (devServerConfig: AngularWebpackDevServerConfig): Promise<PresetHandlerResult> {
const webpackConfig = await getAngularCliWebpackConfig(devServerConfig)
return { frameworkConfig: webpackConfig, sourceWebpackModulesResult: sourceDefaultWebpackDependencies(devServerConfig) }
}

View File

@@ -1,6 +1,6 @@
import Module from 'module'
import path from 'path'
import type { WebpackDevServerConfig, ALL_FRAMEWORKS } from '../devServer'
import type { Frameworks, WebpackDevServerConfig } from '../devServer'
import debugFn from 'debug'
const debug = debugFn('cypress:webpack-dev-server:sourceRelativeWebpackModules')
@@ -56,7 +56,7 @@ export const cypressWebpackPath = (config: WebpackDevServerConfig) => {
})
}
type FrameworkWebpackMapper = { [Property in typeof ALL_FRAMEWORKS[number]]: string | undefined }
type FrameworkWebpackMapper = { [Property in Frameworks]: string | undefined }
const frameworkWebpackMapper: FrameworkWebpackMapper = {
'create-react-app': 'react-scripts',
@@ -66,6 +66,7 @@ const frameworkWebpackMapper: FrameworkWebpackMapper = {
vue: undefined,
next: 'next',
'angular': '@angular-devkit/build-angular',
'svelte': undefined,
}
// Source the users framework from the provided projectRoot. The framework, if available, will serve

View File

@@ -3,14 +3,15 @@ import chaiPromise from 'chai-as-promised'
import * as fs from 'fs-extra'
import cloneDeep from 'lodash/cloneDeep'
import * as path from 'path'
import { WebpackDevServerConfig } from '../../src/devServer'
import {
angularHandler,
AngularJsonProjectConfig,
AngularWebpackDevServerConfig,
BuildOptions,
generateTsConfig,
getAngularBuildOptions,
getAngularCliModules,
getAngularJson,
getProjectConfig,
getTempDir,
toPosix,
} from '../../src/helpers/angularHandler'
@@ -19,27 +20,6 @@ import { scaffoldMigrationProject } from '../test-helpers/scaffoldProject'
chai.use(chaiPromise)
const projectConfig: AngularJsonProjectConfig = {
root: 'my-root',
sourceRoot: 'my-root/src',
projectType: 'application',
architect: {
build: {
options: {
aot: true,
tsConfig: 'tsconfig.json',
polyfills: 'src/polyfills.ts',
optimization: true,
},
configurations: {
development: {
optimization: false,
},
},
},
},
}
describe('angularHandler', function () {
this.timeout(1000 * 60)
@@ -54,7 +34,7 @@ describe('angularHandler', function () {
specPattern: 'src/**/*.cy.ts',
} as Cypress.PluginConfigOptions,
framework: 'angular',
} as WebpackDevServerConfig
} as AngularWebpackDevServerConfig
const { frameworkConfig: webpackConfig, sourceWebpackModulesResult } = await angularHandler(devServerConfig)
@@ -62,10 +42,12 @@ describe('angularHandler', function () {
expect((webpackConfig?.entry as any).main).to.be.undefined
expect(sourceWebpackModulesResult.framework?.importPath).to.include(path.join('@angular-devkit', 'build-angular'))
const { buildOptions } = await expectNormalizeProjectConfig(projectRoot)
await expectLoadsAngularJson(projectRoot)
await expectLoadsAngularCLiModules(projectRoot)
await expectGeneratesTsConfig(devServerConfig)
expectLoadsAngularBuildOptions()
await expectGeneratesTsConfig(devServerConfig, buildOptions)
expectLoadsAngularBuildOptions(buildOptions)
})
it('sources the config from angular-14', async () => {
@@ -79,7 +61,58 @@ describe('angularHandler', function () {
specPattern: 'src/**/*.cy.ts',
} as Cypress.PluginConfigOptions,
framework: 'angular',
} as WebpackDevServerConfig
} as AngularWebpackDevServerConfig
const { frameworkConfig: webpackConfig, sourceWebpackModulesResult } = await angularHandler(devServerConfig)
expect(webpackConfig).to.exist
expect((webpackConfig?.entry as any).main).to.be.undefined
expect(sourceWebpackModulesResult.framework?.importPath).to.include(path.join('@angular-devkit', 'build-angular'))
const { buildOptions } = await expectNormalizeProjectConfig(projectRoot)
await expectLoadsAngularJson(projectRoot)
await expectLoadsAngularCLiModules(projectRoot)
await expectGeneratesTsConfig(devServerConfig, buildOptions)
expectLoadsAngularBuildOptions(buildOptions)
})
it('allows custom project config', async () => {
const customProjectConfig = {
root: '',
sourceRoot: 'src',
buildOptions: {
outputPath: 'dist/angular',
index: 'src/index.html',
main: 'src/main.ts',
polyfills: 'src/polyfills.ts',
tsConfig: 'tsconfig.app.json',
inlineStyleLanguage: 'scss',
assets: ['src/favicon.ico', 'src/assets'],
styles: ['src/styles.scss'],
scripts: [],
buildOptimizer: false,
optimization: false,
vendorChunk: true,
extractLicenses: false,
sourceMap: true,
namedChunks: true,
},
}
const projectRoot = await scaffoldMigrationProject('angular-custom-config')
process.chdir(projectRoot)
const devServerConfig = {
framework: 'angular',
cypressConfig: {
projectRoot,
specPattern: 'src/**/*.cy.ts',
} as Cypress.PluginConfigOptions,
options: {
projectConfig: customProjectConfig,
},
} as unknown as AngularWebpackDevServerConfig
const { frameworkConfig: webpackConfig, sourceWebpackModulesResult } = await angularHandler(devServerConfig)
@@ -89,11 +122,39 @@ describe('angularHandler', function () {
await expectLoadsAngularJson(projectRoot)
await expectLoadsAngularCLiModules(projectRoot)
await expectGeneratesTsConfig(devServerConfig)
expectLoadsAngularBuildOptions()
await expectGeneratesTsConfig(devServerConfig, customProjectConfig.buildOptions)
expectLoadsAngularBuildOptions(customProjectConfig.buildOptions)
})
})
const expectNormalizeProjectConfig = async (projectRoot: string) => {
const projectConfig = await getProjectConfig(projectRoot)
expect(projectConfig).to.deep.eq({
root: '',
sourceRoot: 'src',
buildOptions: {
outputPath: 'dist/angular',
index: 'src/index.html',
main: 'src/main.ts',
polyfills: 'src/polyfills.ts',
tsConfig: 'tsconfig.app.json',
inlineStyleLanguage: 'scss',
assets: ['src/favicon.ico', 'src/assets'],
styles: ['src/styles.scss'],
scripts: [],
buildOptimizer: false,
optimization: false,
vendorChunk: true,
extractLicenses: false,
sourceMap: true,
namedChunks: true,
},
})
return projectConfig
}
const expectLoadsAngularJson = async (projectRoot: string) => {
const angularJson = await getAngularJson(projectRoot)
@@ -112,29 +173,21 @@ const expectLoadsAngularCLiModules = async (projectRoot: string) => {
await expect(getAngularCliModules(path.join('..', projectRoot))).to.be.rejected
}
const expectLoadsAngularBuildOptions = () => {
const expectLoadsAngularBuildOptions = (buildOptions: BuildOptions) => {
const tsConfig = 'tsconfig.cypress.json'
let buildOptions = getAngularBuildOptions(projectConfig, tsConfig)
let finalBuildOptions = getAngularBuildOptions(buildOptions, tsConfig)
expect(buildOptions.aot).to.be.false
expect(buildOptions.optimization).to.be.false
expect(buildOptions.tsConfig).to.equal(tsConfig)
expect(buildOptions.outputHashing).to.equal('none')
expect(buildOptions.budgets).to.be.undefined
const modifiedProjectConfig = cloneDeep(projectConfig)
delete modifiedProjectConfig.architect.build.configurations
buildOptions = getAngularBuildOptions(modifiedProjectConfig, tsConfig)
expect(buildOptions.optimization).to.be.true
expect(finalBuildOptions.aot).to.be.false
expect(finalBuildOptions.optimization).to.be.false
expect(finalBuildOptions.tsConfig).to.equal(tsConfig)
expect(finalBuildOptions.outputHashing).to.equal('none')
expect(finalBuildOptions.budgets).to.be.undefined
}
const expectGeneratesTsConfig = async (devServerConfig: WebpackDevServerConfig) => {
const expectGeneratesTsConfig = async (devServerConfig: AngularWebpackDevServerConfig, buildOptions: any) => {
const { projectRoot } = devServerConfig.cypressConfig
let tsConfigPath = await generateTsConfig(devServerConfig, projectConfig)
let tsConfigPath = await generateTsConfig(devServerConfig, buildOptions)
const tempDir = await getTempDir()
expect(tsConfigPath).to.eq(path.join(tempDir, 'tsconfig.json'))
@@ -142,7 +195,8 @@ const expectGeneratesTsConfig = async (devServerConfig: WebpackDevServerConfig)
let tsConfig = JSON.parse(await fs.readFile(tsConfigPath, 'utf8'))
expect(tsConfig).to.deep.eq({
extends: toPosix(path.join(projectRoot, 'tsconfig.json')),
// verifies the default `tsconfig.app.json` is extended
extends: toPosix(path.join(projectRoot, 'tsconfig.app.json')),
compilerOptions: {
outDir: toPosix(path.join(projectRoot, 'out-tsc/cy')),
allowSyntheticDefaultImports: true,
@@ -155,21 +209,31 @@ const expectGeneratesTsConfig = async (devServerConfig: WebpackDevServerConfig)
],
})
const modifiedProjectConfig = cloneDeep(projectConfig)
const modifiedBuildOptions = cloneDeep(buildOptions)
delete modifiedProjectConfig.architect.build.options.polyfills
delete modifiedBuildOptions.polyfills
modifiedBuildOptions.tsConfig = 'tsconfig.cy.json'
const modifiedDevServerConfig = cloneDeep(devServerConfig)
const supportFile = path.join(projectRoot, 'cypress', 'support', 'component.ts')
modifiedDevServerConfig.cypressConfig.supportFile = supportFile
tsConfigPath = await generateTsConfig(modifiedDevServerConfig, modifiedProjectConfig)
tsConfigPath = await generateTsConfig(modifiedDevServerConfig, modifiedBuildOptions)
tsConfig = JSON.parse(await fs.readFile(tsConfigPath, 'utf8'))
expect(tsConfig.include).to.deep.equal([
toPosix(path.join(projectRoot, 'src/**/*.cy.ts')),
toPosix(supportFile),
toPosix(path.join(projectRoot, 'node_modules/cypress/types/index.d.ts')),
])
expect(tsConfig).to.deep.eq({
// verifies the custom `tsconfig.cy.json` is extended
extends: toPosix(path.join(projectRoot, 'tsconfig.cy.json')),
compilerOptions: {
outDir: toPosix(path.join(projectRoot, 'out-tsc/cy')),
allowSyntheticDefaultImports: true,
skipLibCheck: true,
},
include: [
toPosix(path.join(projectRoot, 'src/**/*.cy.ts')),
toPosix(supportFile),
toPosix(path.join(projectRoot, 'node_modules/cypress/types/index.d.ts')),
],
})
}

View File

@@ -1,7 +1,7 @@
import Fixtures, { ProjectFixtureDir } from '@tooling/system-tests'
import * as FixturesScaffold from '@tooling/system-tests/lib/dep-installer'
export async function scaffoldMigrationProject (project: ProjectFixtureDir) {
export async function scaffoldMigrationProject (project: ProjectFixtureDir): Promise<string> {
Fixtures.removeProject(project)
await Fixtures.scaffoldProject(project)

View File

@@ -9,8 +9,8 @@
"types": "tsc --noEmit --lib es2015,dom --types cypress cypress/e2e/*.ts"
},
"devDependencies": {
"ts-loader": "7.0.4",
"typescript": "^4.2.3"
"ts-loader": "8.4.0",
"typescript": "^4.7.4"
},
"license": "ISC",
"keywords": []

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