diff --git a/.circleci/config.yml b/.circleci/config.yml index 0a3ec0f42a..89944475ba 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,5 +1,8 @@ version: 2.1 +orbs: + cypress: cypress-io/cypress@3 + executors: node-executor: resource_class: medium+ @@ -51,7 +54,27 @@ jobs: - attach_workspace: at: ~/repo - run: npm run lint - + + install-and-persist: + executor: cypress/default + steps: + - cypress/install + - persist_to_workspace: + root: ~/ + paths: + - .cache/Cypress + - project + + run-tests-in-parallel: + executor: cypress/default + parallelism: 6 + steps: + - attach_workspace: + at: ~/ + - cypress/run-tests: + start-command: 'npm run start' + cypress-command: 'npx wait-on http://localhost:3000 && npx cypress run --parallel --record' + release: <<: *job-defaults steps: @@ -67,7 +90,6 @@ jobs: node ./scripts/search/scrape-and-compare-algolia-index.mjs workflows: - version: 2 build-and-test: jobs: - build @@ -75,7 +97,13 @@ workflows: name: "Lint JS/CSS/Markdown" requires: - build - + - install-and-persist: + name: Install & Persist To Workspace + - run-tests-in-parallel: + name: Run Tests in Parallel + requires: + - build + - Install & Persist To Workspace # Run Algolia scraper only on main. - release: name: 'Run Algolia scraper' diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 0000000000..29b85f6ec8 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,47 @@ +import { defineConfig } from "cypress"; +import { readdirSync } from 'fs' +import { join } from 'path' + +export default defineConfig({ + projectId: 'imown1', + fixturesFolder: false, + viewportHeight: 800, + viewportWidth: 1200, + experimentalMemoryManagement: true, + e2e: { + supportFile: false, + baseUrl: "http://localhost:3000", + setupNodeEvents(on, config) { + on("task", { + "createFileTree"({ path }) { + // take the given path and create an array of all file paths + // with each files and the directory path of the file as strings + // for example: given 'accessibility' return + // ['accessibility/get-started/introduction', 'accessibility/core-concepts/how-it-works'] + path = 'docs/' + path; + + function walk(dir: string): string[] { + return readdirSync(dir, { withFileTypes: true }).flatMap((file) => { + if (file.name.includes('_category_.json') || file.name.includes('.DS_Store')) { + return [] + } + + if (file.name.includes('.mdx')) { + // remove the .mdx file extension + file.name = file.name.slice(0, -4) + } + + if (file.isDirectory()) { + return walk(join(dir, file.name)) + } else { + return [join(dir, file.name)] + } + }) + } + + return walk(path).filter((file) => file !== undefined).map((file) => file.slice(5)) + } + }) + } + }, +}); diff --git a/cypress/e2e/a11y_pages.cy.ts b/cypress/e2e/a11y_pages.cy.ts new file mode 100644 index 0000000000..303f649c16 --- /dev/null +++ b/cypress/e2e/a11y_pages.cy.ts @@ -0,0 +1,16 @@ +describe('Visit a11y pages', () => { + before(() => { + cy.task('createFileTree', { path: 'accessibility' }).then((urls) => { + // console.log('urlsLength', urls.length) + Cypress.env({ urls }) + }) + }) + + // Well, this number is hardcoded and should match the length of urls + Cypress._.range(0, 12).forEach(index => { + it(`Visit a11y page ${index} `, () => { + cy.visit(Cypress.env().urls[index]) + cy.get('h1').should('be.visible') + }) + }) +}) \ No newline at end of file diff --git a/cypress/e2e/api_pages.cy.ts b/cypress/e2e/api_pages.cy.ts new file mode 100644 index 0000000000..72dfef7116 --- /dev/null +++ b/cypress/e2e/api_pages.cy.ts @@ -0,0 +1,16 @@ +describe('Visit API pages', () => { + before(() => { + cy.task('createFileTree', { path: 'api' }).then((urls) => { + // console.log('urlsLength', urls.length) + Cypress.env({ urls }) + }) + }) + + // Well, this number is hardcoded and should match the length of urls + Cypress._.range(0, 132).forEach(index => { + it(`Visit API page ${index} `, () => { + cy.visit(Cypress.env().urls[index]) + cy.get('h1').should('be.visible') + }) + }) +}) \ No newline at end of file diff --git a/cypress/e2e/app_pages.cy.ts b/cypress/e2e/app_pages.cy.ts new file mode 100644 index 0000000000..a0026f4ac9 --- /dev/null +++ b/cypress/e2e/app_pages.cy.ts @@ -0,0 +1,16 @@ +describe('Visit App pages', () => { + before(() => { + cy.task('createFileTree', { path: 'app' }).then((urls) => { + console.log('urlsLength', urls.length) + Cypress.env({ urls }) + }) + }) + + // Well, this number is hardcoded and should match the length of urls + Cypress._.range(0, 81).forEach(index => { + it(`Visit App page ${index} `, () => { + cy.visit(Cypress.env().urls[index]) + cy.get('h1').should('be.visible') + }) + }) +}) \ No newline at end of file diff --git a/cypress/e2e/basic_tests.cy.ts b/cypress/e2e/basic_tests.cy.ts new file mode 100644 index 0000000000..71f9e21c0c --- /dev/null +++ b/cypress/e2e/basic_tests.cy.ts @@ -0,0 +1,64 @@ +describe('Basic tests', () => { + beforeEach(() => { + cy.visit('/') + }) + + it('root reroutes to App ', () => { + cy.url().should('include', '/app/get-started/why-cypress') + }) + + describe('Main Nav', () => { + it('App', () => { + cy.get('nav a').contains('App').click() + cy.url().should('include', '/app/get-started/why-cypress') + cy.get('[aria-current="page"]').should('contain', 'App') + }) + + it('API', () => { + cy.get('nav a').contains('API').click() + cy.url().should('include', '/api/table-of-contents') + cy.get('[aria-current="page"]').should('contain', 'API') + }) + + it('Cloud', () => { + cy.get('nav a').contains('Cloud').click() + cy.url().should('include', '/cloud/get-started/introduction') + cy.get('[aria-current="page"]').should('contain', 'Cloud') + }) + + it('UI Coverage', () => { + cy.get('nav a').contains('UI Coverage').click() + cy.url().should('include', '/ui-coverage/get-started/introduction') + cy.get('[aria-current="page"]').should('contain', 'UI Coverage') + }) + + it('Accessibility', () => { + cy.get('nav a').contains('Accessibility').click() + cy.url().should('include', '/accessibility/get-started/introduction') + cy.get('[aria-current="page"]').should('contain', 'Accessibility') + }) + }) + + describe('Announcement Bar', () => { + it('should close when clicking close btn', () => { + cy.get('[role="banner"]').should('be.visible') + cy.get('.close').click() + cy.get('[role="banner"]').should('not.exist') + }) + }) + + describe('Dark mode', () => { + it('switch to dark mode when clicked', () => { + cy.get('[data-theme=light]').should('have.css', 'background-color', 'rgb(255, 255, 255)') // white + cy.get('[aria-label="Switch to dark mode"]').click() + cy.get('[data-theme=dark]').should('have.css', 'background-color', 'rgb(27, 30, 46)') // dark gray + }) + }) + + describe('Search', () => { + it('search opens search popup', () => { + cy.contains('Search ⌘K').click() + cy.get('.DocSearch-Modal').should('be.visible') + }) + }) +}) \ No newline at end of file diff --git a/cypress/e2e/cloud_pages.cy.ts b/cypress/e2e/cloud_pages.cy.ts new file mode 100644 index 0000000000..21d3ced782 --- /dev/null +++ b/cypress/e2e/cloud_pages.cy.ts @@ -0,0 +1,16 @@ +describe('Visit Cloud pages', () => { + before(() => { + cy.task('createFileTree', { path: 'cloud' }).then((urls) => { + // console.log('urlsLength', urls.length) + Cypress.env({ urls }) + }) + }) + + // Well, this number is hardcoded and should match the length of urls + Cypress._.range(0, 29).forEach(index => { + it(`Visit Cloud page ${index} `, () => { + cy.visit(Cypress.env().urls[index]) + cy.get('h1').should('be.visible') + }) + }) +}) \ No newline at end of file diff --git a/cypress/e2e/ui_cov_pages.cy.ts b/cypress/e2e/ui_cov_pages.cy.ts new file mode 100644 index 0000000000..089713117f --- /dev/null +++ b/cypress/e2e/ui_cov_pages.cy.ts @@ -0,0 +1,16 @@ +describe('Visit UI Cov pages', () => { + before(() => { + cy.task('createFileTree', { path: 'ui-coverage' }).then((urls) => { + console.log('urlsLength', urls.length) + Cypress.env({ urls }) + }) + }) + + // Well, this number is hardcoded and should match the length of urls + Cypress._.range(0, 16).forEach(index => { + it(`Visit UI Cov page ${index} `, () => { + cy.visit(Cypress.env().urls[index]) + cy.get('h1').should('be.visible') + }) + }) +}) \ No newline at end of file diff --git a/docs/accessibility/core-concepts/how-it-works.mdx b/docs/accessibility/core-concepts/how-it-works.mdx index 23d44d5a82..8340cb6231 100644 --- a/docs/accessibility/core-concepts/how-it-works.mdx +++ b/docs/accessibility/core-concepts/how-it-works.mdx @@ -29,6 +29,6 @@ This means that a 100% axe score does not mean all possible accessibility errors The value of this form of testing in Cypress Accessibility is to give you fast, reliable, easy-to-understand feedback about common accessibility mistakes that are found in most projects. Providing these results automatically as part of your test run means that you can find and fix these issues with minimal friction, shifting accessibility left in your software development lifecycle. -## Test Replay limitations +## Powered by Test Replay -Because Cypress Accessibility uses data captured through the Test Replay protocol, it is subject to any [limitations of that protocol](https://docs.cypress.io/faq/questions/cloud-faq#Is-everything-captured-and-replayed-in-Test-Replay) related to data capture or browser support. +Because Cypress Accessibility uses data captured through Cypress Test Replay, it is subject [Test Replay limitations](https://docs.cypress.io/faq/questions/cloud-faq#Is-everything-captured-and-replayed-in-Test-Replay). diff --git a/docs/accessibility/get-started/introduction.mdx b/docs/accessibility/get-started/introduction.mdx index 287a77cc92..1884e48161 100644 --- a/docs/accessibility/get-started/introduction.mdx +++ b/docs/accessibility/get-started/introduction.mdx @@ -23,6 +23,12 @@ Cypress Accessibility generates a sortable, filterable report, to see scores and ## How are reports created? +:::caution + +Cypress Accessibility generates reports using [Cypress Test Replay](/cloud/features/test-replay) data and requires Cypress v13+. + +::: + Cypress Cloud generates an accessibility report for each unique state reached during your tests on a given Cypress run. This includes pages visited in end-to-end tests, as well as individual components rendered in component tests, and any states or pages that were encountered as a result of interactions performed during test execution. All of these page or component states are detected using the same protocol that powers [Test Replay](/cloud/features/test-replay), meaning no additional code or setup is needed anywhere in your tests. diff --git a/docs/api/commands/origin.mdx b/docs/api/commands/origin.mdx index a674dd0d17..d98a40bd00 100644 --- a/docs/api/commands/origin.mdx +++ b/docs/api/commands/origin.mdx @@ -339,7 +339,10 @@ Cypress.Commands.add('login', (username, password) => { ### How to Test Multiple Origins - + In this video we walk through how to test multiple origins in a single test. We also look at how to use the `cy.session()` command to cache session information diff --git a/docs/api/commands/scrollintoview.mdx b/docs/api/commands/scrollintoview.mdx index 0d99f5ea67..b3982fc96b 100644 --- a/docs/api/commands/scrollintoview.mdx +++ b/docs/api/commands/scrollintoview.mdx @@ -1,6 +1,5 @@ --- title: scrollIntoView -slug: /api/commands/scrollIntoView --- Scroll an element into view. @@ -131,4 +130,4 @@ console outputs the following: ## See also -- [`cy.scrollTo()`](/api/commands/scrollTo) +- [`cy.scrollTo()`](/api/commands/scrollto) diff --git a/docs/api/commands/scrollto.mdx b/docs/api/commands/scrollto.mdx index 6a88a6b210..e21e02d511 100644 --- a/docs/api/commands/scrollto.mdx +++ b/docs/api/commands/scrollto.mdx @@ -1,6 +1,5 @@ --- title: scrollTo -slug: /api/commands/scrollTo --- Scroll to a specific position. @@ -224,4 +223,4 @@ following: ## See also -- [`.scrollIntoView()`](/api/commands/scrollIntoView) +- [`.scrollIntoView()`](/api/commands/scrollintoview) diff --git a/docs/api/commands/session.mdx b/docs/api/commands/session.mdx index f096d4bc1b..cd702490df 100644 --- a/docs/api/commands/session.mdx +++ b/docs/api/commands/session.mdx @@ -583,7 +583,7 @@ isolation is enabled with `testIsolation=true` (default in Cypress 12), This guarantees consistent behavior whether a session is being created or restored and allows you to switch sessions without first having to explicitly log out. -| | Page cleared (test) | Session data cleared | +| When cleared? | Page cleared (test) | Session data cleared | | -------------------------- | :----------------------------------------: | :----------------------------------------: | | Before `setup` | | | | Before `cy.session()` ends | | | @@ -596,7 +596,7 @@ to ensure the page to test is loaded. When test isolation is disabled with `testIsolation=false`, the page will not clear, however, the session data will clear when `cy.session()` runs. -| | Page cleared (test) | Session data cleared | +| When cleared | Page cleared (test) | Session data cleared | | -------------------------- | :-----------------: | :----------------------------------------: | | Before `setup` | | | | Before `cy.session()` ends | | | diff --git a/docs/api/cypress-api/custom-commands.mdx b/docs/api/cypress-api/custom-commands.mdx index 2b23d48a51..272cd69b06 100644 --- a/docs/api/cypress-api/custom-commands.mdx +++ b/docs/api/cypress-api/custom-commands.mdx @@ -405,7 +405,7 @@ with an existing subject or without one. Examples of dual commands: - [`cy.screenshot()`](/api/commands/screenshot) -- [`cy.scrollTo()`](/api/commands/scrollTo) +- [`cy.scrollTo()`](/api/commands/scrollto) - [`cy.wait()`](/api/commands/wait) #### Custom Dual Command @@ -497,7 +497,10 @@ cy.get('#username').type('username@email.com') cy.get('#password').type('superSecret123') ``` - + You may want to mask some values passed to the [.type()](/api/commands/type) command so that sensitive data does not display in screenshots or videos of your @@ -529,7 +532,10 @@ cy.get('#password').type('superSecret123', { sensitive: true }) Now our sensitive password is not printed to the Cypress Command Log when `sensitive: true` is passed as an option to [.type()](/api/commands/type). - + #### Overwrite `screenshot` command diff --git a/docs/api/table-of-contents.mdx b/docs/api/table-of-contents.mdx index b858aabee2..754503a3ee 100644 --- a/docs/api/table-of-contents.mdx +++ b/docs/api/table-of-contents.mdx @@ -93,8 +93,8 @@ Learn more about action commands in our | [`.click()`](/api/commands/click) | Click a DOM element. | | [`.dblclick()`](/api/commands/dblclick) | Double-click a DOM element. | | [`.rightclick()`](/api/commands/rightclick) | Right click a DOM element. | -| [`.scrollIntoView()`](/api/commands/scrollIntoView) | Scroll an element into view. | -| [`.scrollTo()`](/api/commands/scrollTo) | Scroll to a specific position. | +| [`.scrollIntoView()`](/api/commands/scrollintoview) | Scroll an element into view. | +| [`.scrollTo()`](/api/commands/scrollto) | Scroll to a specific position. | | [`.select()`](/api/commands/select) | Select an `