From 5cecf691c2408c0a13eea33207830ecdefc3ee5a Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Fri, 10 Mar 2023 16:22:51 -0500 Subject: [PATCH] Minimal tests with Playwright (#35322) --- .../actions/setup-elasticsearch/action.yml | 24 ++++ .github/workflows/browser-test.yml | 15 +-- .github/workflows/headless-tests.yml | 53 ++++++++ .github/workflows/sync-search-pr.yml | 12 +- .github/workflows/test.yml | 17 +-- .gitignore | 6 +- package-lock.json | 126 ++++++++++++++++-- package.json | 4 + playwright.config.ts | 94 +++++++++++++ .../github-docs-dotcom-en-records.json | 9 ++ .../content/get-started/foo/for-playwright.md | 17 +++ .../fixtures/content/get-started/foo/index.md | 1 + .../playwright-rendering.spec.ts | 55 ++++++++ 13 files changed, 387 insertions(+), 46 deletions(-) create mode 100644 .github/actions/setup-elasticsearch/action.yml create mode 100644 .github/workflows/headless-tests.yml create mode 100644 playwright.config.ts create mode 100644 tests/fixtures/content/get-started/foo/for-playwright.md create mode 100644 tests/rendering-fixtures/playwright-rendering.spec.ts diff --git a/.github/actions/setup-elasticsearch/action.yml b/.github/actions/setup-elasticsearch/action.yml new file mode 100644 index 0000000000..01c0f83a2b --- /dev/null +++ b/.github/actions/setup-elasticsearch/action.yml @@ -0,0 +1,24 @@ +name: Set up local Elasticsearch + +description: Install a local Elasticseach with version that matches prod + +inputs: + token: + description: PAT + required: true + +runs: + using: 'composite' + steps: + - name: Install a local Elasticsearch for testing + # For the sake of saving time, only run this step if the test-group + # is one that will run tests against an Elasticsearch on localhost. + uses: getong/elasticsearch-action@95b501ab0c83dee0aac7c39b7cea3723bef14954 + with: + # Make sure this matches production and `sync-search-pr.yml` + elasticsearch version: '7.11.1' + host port: 9200 + container port: 9200 + host node port: 9300 + node port: 9300 + discovery type: 'single-node' diff --git a/.github/workflows/browser-test.yml b/.github/workflows/browser-test.yml index 4631fe15be..40521f5f95 100644 --- a/.github/workflows/browser-test.yml +++ b/.github/workflows/browser-test.yml @@ -35,22 +35,11 @@ jobs: if: github.repository == 'github/docs-internal' || github.repository == 'github/docs' runs-on: ${{ fromJSON('["ubuntu-latest", "ubuntu-20.04-xl"]')[github.repository == 'github/docs-internal'] }} steps: - - name: Install a local Elasticsearch for testing - # For the sake of saving time, only run this step if the test-group - # is one that will run tests against an Elasticsearch on localhost. - uses: getong/elasticsearch-action@95b501ab0c83dee0aac7c39b7cea3723bef14954 - with: - # Make sure this matches production and `sync-search-pr.yml` - elasticsearch version: '7.11.1' - host port: 9200 - container port: 9200 - host node port: 9300 - node port: 9300 - discovery type: 'single-node' - - name: Checkout uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: ./.github/actions/setup-elasticsearch + - name: Setup Node.js uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 with: diff --git a/.github/workflows/headless-tests.yml b/.github/workflows/headless-tests.yml new file mode 100644 index 0000000000..5d5c7e5818 --- /dev/null +++ b/.github/workflows/headless-tests.yml @@ -0,0 +1,53 @@ +name: Headless Tests + +# **What it does**: This runs our browser tests to test things that depend +# on client-side JavaScript. +# **Why we have it**: Because most automated jest tests only test static +# input and outputs. +# **Who does it impact**: Docs engineering, open-source engineering contributors. + +on: + workflow_dispatch: + merge_group: + pull_request: + +permissions: + contents: read + +# This allows a subsequently queued workflow run to interrupt previous runs +concurrency: + group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' + cancel-in-progress: true + +env: + ELASTICSEARCH_URL: http://localhost:9200/ + +jobs: + playwright-tests: + if: github.repository == 'github/docs-internal' || github.repository == 'github/docs' + runs-on: ${{ fromJSON('["ubuntu-latest", "ubuntu-20.04-xl"]')[github.repository == 'github/docs-internal'] }} + timeout-minutes: 60 + steps: + - name: Check out repo + uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + + - uses: ./.github/actions/setup-elasticsearch + + - uses: ./.github/actions/node-npm-setup + + - name: Cache nextjs build + uses: actions/cache@9b0c1fce7a93df8e3bb8926b0d6e9d89e92f20a7 + with: + path: .next/cache + key: ${{ runner.os }}-nextjs-${{ hashFiles('package*.json') }} + + - name: Run build script + run: npm run build + + - name: Index fixtures into the local Elasticsearch + run: npm run index-test-fixtures + + - name: Run Playwright tests + env: + PLAYWRIGHT_WORKERS: ${{ fromJSON('[1, 4]')[github.repository == 'github/docs-internal'] }} + run: npm run playwright-test -- --reporter list diff --git a/.github/workflows/sync-search-pr.yml b/.github/workflows/sync-search-pr.yml index 56ce51e8c8..9115ad1cb6 100644 --- a/.github/workflows/sync-search-pr.yml +++ b/.github/workflows/sync-search-pr.yml @@ -33,19 +33,11 @@ jobs: runs-on: ${{ fromJSON('["ubuntu-latest", "ubuntu-20.04-xl"]')[github.repository == 'github/docs-internal'] }} if: github.repository == 'github/docs-internal' || github.repository == 'github/docs' steps: - - uses: getong/elasticsearch-action@95b501ab0c83dee0aac7c39b7cea3723bef14954 - with: - # # Make sure this matches production and `test.yml` - elasticsearch version: '7.11.1' - host port: 9200 - container port: 9200 - host node port: 9300 - node port: 9300 - discovery type: 'single-node' - - name: Check out repo uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: ./.github/actions/setup-elasticsearch + - uses: ./.github/actions/node-npm-setup - name: Cache nextjs build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a4a072b548..6e318fc8d9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -67,25 +67,14 @@ jobs: matrix: test-group: ${{ fromJSON(needs.figureOutMatrix.outputs.matrix) }} steps: - - name: Install a local Elasticsearch for testing - # For the sake of saving time, only run this step if the test-group - # is one that will run tests against an Elasticsearch on localhost. - if: ${{ matrix.test-group == 'content' || matrix.test-group == 'translations' }} - uses: getong/elasticsearch-action@95b501ab0c83dee0aac7c39b7cea3723bef14954 - with: - # Make sure this matches production and `sync-search-pr.yml` - elasticsearch version: '7.11.1' - host port: 9200 - container port: 9200 - host node port: 9300 - node port: 9300 - discovery type: 'single-node' - # Each of these ifs needs to be repeated at each step to make sure the required check still runs # Even if if doesn't do anything - name: Check out repo uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 + - uses: ./.github/actions/setup-elasticsearch + if: ${{ matrix.test-group == 'content' || matrix.test-group == 'translations' }} + - uses: ./.github/actions/node-npm-setup - uses: ./.github/actions/get-docs-early-access diff --git a/.gitignore b/.gitignore index 171f56a24d..4602528ad1 100644 --- a/.gitignore +++ b/.gitignore @@ -33,8 +33,12 @@ user-code/ script/logs/ external-link-checker-db.json +# Playwright related +/test-results/ +/playwright-report/ +/playwright/.cache/ + # Automated content source rest-api-description .installed.package-lock.json - diff --git a/package-lock.json b/package-lock.json index b9c324e79b..e24993fa33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -108,6 +108,7 @@ "@jest/globals": "29.4.3", "@octokit/graphql": "5.0.4", "@octokit/rest": "^19.0.4", + "@playwright/test": "1.31.2", "@types/github-slugger": "^2.0.0", "@types/imurmurhash": "^0.1.1", "@types/js-cookie": "^3.0.2", @@ -3944,6 +3945,25 @@ "@octokit/openapi-types": "^11.2.0" } }, + "node_modules/@playwright/test": { + "version": "1.31.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.31.2.tgz", + "integrity": "sha512-BYVutxDI4JeZKV1+ups6dt5WiqKhjBtIYowyZIJ3kBDmJgsuPKsqqKNIMFbUePLSCmp2cZu+BDL427RcNKTRYw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.31.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, "node_modules/@pnpm/network.ca-file": { "version": "1.0.1", "dev": true, @@ -3986,14 +4006,16 @@ }, "node_modules/@primer/octicons": { "version": "18.1.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-18.1.0.tgz", + "integrity": "sha512-kG+J+cPLL9lV8K5i8Dh1E0qs7NgHAX6kTOD4YaUbBKpeHqpZfFxk2ce8mVsIe5Yc0uN+FUkb2o3W90E3u5F35g==", "dependencies": { "object-assign": "^4.1.1" } }, "node_modules/@primer/octicons-react": { "version": "18.1.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-18.1.0.tgz", + "integrity": "sha512-tB/mkoA5PG7aqvc8agRwFlO63rlI/D4l+QsdRYjkkKfjc2lhtmIzGS/WzAcmIAbToa+qGRSokj2KC1tCG9gQBw==", "engines": { "node": ">=8" }, @@ -4046,6 +4068,17 @@ "styled-components": "4.x || 5.x" } }, + "node_modules/@primer/react/node_modules/@primer/octicons-react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-18.2.0.tgz", + "integrity": "sha512-taT0l99qztqU9NnNo0HCutm3mEjBmXcaqWlmCLvU2MrKVrMZF3HuwaguPhNl/rNk8+mpgtDSTTDPBlpI0MtPPA==", + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=15" + } + }, "node_modules/@primer/view-components": { "version": "0.0.120", "resolved": "https://registry.npmjs.org/@primer/view-components/-/view-components-0.0.120.tgz", @@ -8951,7 +8984,8 @@ }, "node_modules/fast-xml-parser": { "version": "4.1.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.3.tgz", + "integrity": "sha512-LsNDahCiCcJPe8NO7HijcnukHB24tKbfDDA5IILx9dmW3Frb52lhbeX6MPNUSvyGNfav2VTYpJ/OqkRoVLrh2Q==", "dependencies": { "strnum": "^1.0.5" }, @@ -9019,7 +9053,8 @@ }, "node_modules/file-type": { "version": "18.2.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.2.1.tgz", + "integrity": "sha512-Yw5MtnMv7vgD2/6Bjmmuegc8bQEVA9GmAyaR18bMYWKqsWDG9wgYZ1j4I6gNMF5Y5JBDcUcjRQqNQx7Y8uotcg==", "dependencies": { "readable-web-to-node-stream": "^3.0.2", "strtok3": "^7.0.0", @@ -9366,6 +9401,20 @@ "devOptional": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "license": "MIT" @@ -10879,7 +10928,8 @@ }, "node_modules/is-svg": { "version": "5.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-5.0.0.tgz", + "integrity": "sha512-sRl7J0oX9yUNamSdc8cwgzh9KBLnQXNzGmW0RVHwg/jEYjGNYHC6UvnYD8+hAeut9WwxRvhG9biK7g/wDGxcMw==", "dependencies": { "fast-xml-parser": "^4.1.3" }, @@ -15244,8 +15294,9 @@ }, "node_modules/nodemon": { "version": "2.0.21", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.21.tgz", + "integrity": "sha512-djN/n2549DUtY33S7o1djRCd7dEm0kBnj9c7S9XVXqRUbuggN1MZH/Nqa+5RFQr63Fbefq37nFXAE9VU86yL1A==", "dev": true, - "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^3.2.7", @@ -15947,6 +15998,18 @@ "node": ">=8" } }, + "node_modules/playwright-core": { + "version": "1.31.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.31.2.tgz", + "integrity": "sha512-a1dFgCNQw4vCsG7bnojZjDnPewZcw7tZUNFN0ZkcLYKj+mPmXvg4MpaaKZ5SgqPsOmqIf2YsVRkgqiRDxD+fDQ==", + "dev": true, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/port-used": { "version": "2.0.8", "license": "MIT", @@ -18393,7 +18456,8 @@ }, "node_modules/strnum": { "version": "1.0.5", - "license": "MIT" + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" }, "node_modules/strtok3": { "version": "7.0.0", @@ -22499,6 +22563,17 @@ "@octokit/openapi-types": "^11.2.0" } }, + "@playwright/test": { + "version": "1.31.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.31.2.tgz", + "integrity": "sha512-BYVutxDI4JeZKV1+ups6dt5WiqKhjBtIYowyZIJ3kBDmJgsuPKsqqKNIMFbUePLSCmp2cZu+BDL427RcNKTRYw==", + "dev": true, + "requires": { + "@types/node": "*", + "fsevents": "2.3.2", + "playwright-core": "1.31.2" + } + }, "@pnpm/network.ca-file": { "version": "1.0.1", "dev": true, @@ -22530,12 +22605,16 @@ }, "@primer/octicons": { "version": "18.1.0", + "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-18.1.0.tgz", + "integrity": "sha512-kG+J+cPLL9lV8K5i8Dh1E0qs7NgHAX6kTOD4YaUbBKpeHqpZfFxk2ce8mVsIe5Yc0uN+FUkb2o3W90E3u5F35g==", "requires": { "object-assign": "^4.1.1" } }, "@primer/octicons-react": { "version": "18.1.0", + "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-18.1.0.tgz", + "integrity": "sha512-tB/mkoA5PG7aqvc8agRwFlO63rlI/D4l+QsdRYjkkKfjc2lhtmIzGS/WzAcmIAbToa+qGRSokj2KC1tCG9gQBw==", "requires": {} }, "@primer/primitives": { @@ -22572,6 +22651,14 @@ "history": "^5.0.0", "react-intersection-observer": "9.4.1", "styled-system": "^5.1.5" + }, + "dependencies": { + "@primer/octicons-react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-18.2.0.tgz", + "integrity": "sha512-taT0l99qztqU9NnNo0HCutm3mEjBmXcaqWlmCLvU2MrKVrMZF3HuwaguPhNl/rNk8+mpgtDSTTDPBlpI0MtPPA==", + "requires": {} + } } }, "@primer/view-components": { @@ -25890,6 +25977,8 @@ }, "fast-xml-parser": { "version": "4.1.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.1.3.tgz", + "integrity": "sha512-LsNDahCiCcJPe8NO7HijcnukHB24tKbfDDA5IILx9dmW3Frb52lhbeX6MPNUSvyGNfav2VTYpJ/OqkRoVLrh2Q==", "requires": { "strnum": "^1.0.5" } @@ -25935,6 +26024,8 @@ }, "file-type": { "version": "18.2.1", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.2.1.tgz", + "integrity": "sha512-Yw5MtnMv7vgD2/6Bjmmuegc8bQEVA9GmAyaR18bMYWKqsWDG9wgYZ1j4I6gNMF5Y5JBDcUcjRQqNQx7Y8uotcg==", "requires": { "readable-web-to-node-stream": "^3.0.2", "strtok3": "^7.0.0", @@ -26148,6 +26239,13 @@ "version": "1.0.0", "devOptional": true }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1" }, @@ -27045,6 +27143,8 @@ }, "is-svg": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-5.0.0.tgz", + "integrity": "sha512-sRl7J0oX9yUNamSdc8cwgzh9KBLnQXNzGmW0RVHwg/jEYjGNYHC6UvnYD8+hAeut9WwxRvhG9biK7g/wDGxcMw==", "requires": { "fast-xml-parser": "^4.1.3" } @@ -29874,6 +29974,8 @@ }, "nodemon": { "version": "2.0.21", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.21.tgz", + "integrity": "sha512-djN/n2549DUtY33S7o1djRCd7dEm0kBnj9c7S9XVXqRUbuggN1MZH/Nqa+5RFQr63Fbefq37nFXAE9VU86yL1A==", "dev": true, "requires": { "chokidar": "^3.5.2", @@ -30295,6 +30397,12 @@ } } }, + "playwright-core": { + "version": "1.31.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.31.2.tgz", + "integrity": "sha512-a1dFgCNQw4vCsG7bnojZjDnPewZcw7tZUNFN0ZkcLYKj+mPmXvg4MpaaKZ5SgqPsOmqIf2YsVRkgqiRDxD+fDQ==", + "dev": true + }, "port-used": { "version": "2.0.8", "requires": { @@ -31810,7 +31918,9 @@ "dev": true }, "strnum": { - "version": "1.0.5" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" }, "strtok3": { "version": "7.0.0", diff --git a/package.json b/package.json index 3e8c454cac..293cacf00c 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "@jest/globals": "29.4.3", "@octokit/graphql": "5.0.4", "@octokit/rest": "^19.0.4", + "@playwright/test": "1.31.2", "@types/github-slugger": "^2.0.0", "@types/imurmurhash": "^0.1.1", "@types/js-cookie": "^3.0.2", @@ -192,9 +193,11 @@ "debug": "cross-env NODE_ENV=development ENABLED_LANGUAGES=en nodemon --inspect server.js", "dev": "cross-env npm start", "fixture-dev": "cross-env ROOT=tests/fixtures npm start", + "fixture-test": "cross-env ROOT=tests/fixtures npm test -- tests/rendering-fixtures", "index-test-fixtures": "node script/search/index-elasticsearch.js -l en -l ja -V ghae -V dotcom --index-prefix tests -- tests/content/fixtures/search-indexes", "lint": "eslint '**/*.{js,mjs,ts,tsx}'", "lint-translation": "cross-env NODE_OPTIONS=--experimental-vm-modules jest tests/linting/lint-files.js", + "playwright-test": "playwright test --project=\"Google Chrome\"", "prepare": "husky install", "prettier": "prettier -w \"**/*.{ts,tsx,js,mjs,scss,yml,yaml}\"", "prettier-check": "prettier -c \"**/*.{ts,tsx,js,mjs,scss,yml,yaml}\"", @@ -204,6 +207,7 @@ "prestart": "node script/cmp-files.js package-lock.json .installed.package-lock.json || npm install && cp package-lock.json .installed.package-lock.json", "start": "cross-env NODE_ENV=development ENABLED_LANGUAGES=en nodemon server.js", "start-all-languages": "cross-env NODE_ENV=development nodemon server.js", + "start-for-playwright": "cross-env ROOT=tests/fixtures NODE_ENV=test node server.js", "sync-search": "cross-env NODE_OPTIONS='--max_old_space_size=8192' start-server-and-test sync-search-server 4002 sync-search-indices", "sync-search-ghes-release": "cross-env GHES_RELEASE=1 start-server-and-test sync-search-server 4002 sync-search-indices", "sync-search-indices": "script/search/sync-search-indices.js", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000000..0a6c2b50d3 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,94 @@ +import { defineConfig, devices } from '@playwright/test' + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Maximum time one test can run for. */ + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.PLAYWRIGHT_WORKERS + ? JSON.parse(process.env.PLAYWRIGHT_WORKERS) + : process.env.CI + ? 1 + : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + // reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ + actionTimeout: 0, + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:4000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { channel: 'msedge' }, + // }, + { + name: 'Google Chrome', + use: { channel: 'chrome' }, + }, + ], + + /* Folder for test artifacts such as screenshots, videos, traces, etc. */ + // outputDir: 'test-results/', + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run start-for-playwright', + port: 4000, + }, +}) diff --git a/tests/content/fixtures/search-indexes/github-docs-dotcom-en-records.json b/tests/content/fixtures/search-indexes/github-docs-dotcom-en-records.json index 8a13243597..6bdc7eba40 100644 --- a/tests/content/fixtures/search-indexes/github-docs-dotcom-en-records.json +++ b/tests/content/fixtures/search-indexes/github-docs-dotcom-en-records.json @@ -28,5 +28,14 @@ "topics": [], "popularity": 0.1, "intro": "" + }, + "/en/get-started/foo/for-playwright": { + "objectID": "/en/get-started/foo/for-playwright", + "breadcrumbs": "Get started Foo", + "title": "For Playwright", + "headings": "Opening", + "content": "This page exists to serve a Playwright test to view an article page", + "popularity": 0.5, + "intro": "Exists for a Playwright test" } } diff --git a/tests/fixtures/content/get-started/foo/for-playwright.md b/tests/fixtures/content/get-started/foo/for-playwright.md new file mode 100644 index 0000000000..626afb6527 --- /dev/null +++ b/tests/fixtures/content/get-started/foo/for-playwright.md @@ -0,0 +1,17 @@ +--- +title: For Playwright +intro: Exists for a Playwright test +versions: + fpt: '*' + ghes: '*' + ghae: '*' + ghec: '*' +--- + +## Opening + +This page exists to serve a Playwright test to view an article page. + +## Second heading + +This is the second heading. diff --git a/tests/fixtures/content/get-started/foo/index.md b/tests/fixtures/content/get-started/foo/index.md index 3d0213d0d4..5c8748bde5 100644 --- a/tests/fixtures/content/get-started/foo/index.md +++ b/tests/fixtures/content/get-started/foo/index.md @@ -13,4 +13,5 @@ children: - /typo-autotitling - /cross-version-linking - /single-image + - /for-playwright --- diff --git a/tests/rendering-fixtures/playwright-rendering.spec.ts b/tests/rendering-fixtures/playwright-rendering.spec.ts new file mode 100644 index 0000000000..b945271fd2 --- /dev/null +++ b/tests/rendering-fixtures/playwright-rendering.spec.ts @@ -0,0 +1,55 @@ +import dotenv from 'dotenv' +import { test, expect } from '@playwright/test' + +// This exists for the benefit of local testing. +// In GitHub Actions, we rely on setting the environment variable directly +// but for convenience, for local development, engineers might have a +// .env file that can set environment variable. E.g. ELASTICSEARCH_URL. +// The `start-server.js` script uses dotenv too, but since Playwright +// tests only interface with the server via HTTP, we too need to find +// this out. +dotenv.config() + +const SEARCH_TESTS = !!process.env.ELASTICSEARCH_URL + +test('view home page', async ({ page }) => { + await page.goto('/') + await expect(page).toHaveTitle(/GitHub Documentation/) +}) + +test('view the for-playwright article', async ({ page }) => { + await page.goto('/get-started/foo/for-playwright') + await expect(page).toHaveTitle(/For Playwright - GitHub Docs/) + + // This is the right-hand sidebar mini-toc link + await page.getByRole('link', { name: 'Second heading' }).click() + await expect(page).toHaveURL(/for-playwright#second-heading/) +}) + +test('use sidebar to go to Hello World page', async ({ page }) => { + await page.goto('/') + + await page.getByTestId('sidebar').getByRole('link', { name: 'Get started' }).click() + await expect(page).toHaveTitle(/Getting started with HubGit/) + + await page.getByTestId('product-sidebar-items').getByText('Quickstart').click() + await page.getByTestId('product-sidebar-items').getByRole('link', { name: 'Hello World' }).click() + await expect(page).toHaveURL(/\/en\/get-started\/quickstart\/hello-world/) + await expect(page).toHaveTitle(/Hello World - GitHub Docs/) +}) + +test('do a search from home page and click on "Foo" page', async ({ page }) => { + test.skip(!SEARCH_TESTS, 'No local Elasticsearch, no tests involving search') + + await page.goto('/') + await page.getByTestId('site-search-input').click() + await page.getByTestId('site-search-input').fill('serve playwright') + await page.getByRole('button', { name: 'Search' }).click() + await expect(page).toHaveURL(/\/search\?query=serve\+playwright/) + await expect(page).toHaveTitle(/\d Search results for "serve playwright"/) + + await page.getByRole('link', { name: 'For Playwright' }).click() + + await expect(page).toHaveURL(/\/get-started\/foo\/for-playwright$/) + await expect(page).toHaveTitle(/For Playwright/) +})