From 7dfa3b5bdce7c441ffb7d34497020150a91391e6 Mon Sep 17 00:00:00 2001
From: Fuxing Loh <4266087+fuxingloh@users.noreply.github.com>
Date: Wed, 28 Jun 2023 17:12:39 +0800
Subject: [PATCH] feat(app): setup playwright testing (#52)
#### What this PR does / why we need it:
For e2e testing.
---
.github/labeler.yml | 24 +-----------
.github/labels.yml | 4 --
.github/workflows/ci.yml | 61 +++++++++++++++++++++++++++++
README.md | 36 +++++++++++++++++
app/.gitignore | 4 +-
app/app/detail.e2e.ts | 10 +++++
app/app/detail.tsx | 1 +
app/app/index.e2e.ts | 9 +++++
app/app/index.tsx | 4 +-
app/metro.config.js | 1 +
app/package.json | 2 +
app/playwright.config.js | 55 ++++++++++++++++++++++++++
packages/eslint-config/index.js | 23 +++++++----
packages/eslint-config/package.json | 3 +-
yarn.lock | 22 ++++++++++-
15 files changed, 219 insertions(+), 40 deletions(-)
create mode 100644 app/app/detail.e2e.ts
create mode 100644 app/app/index.e2e.ts
create mode 100644 app/playwright.config.js
diff --git a/.github/labeler.yml b/.github/labeler.yml
index f3714fb..6aea741 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -36,30 +36,10 @@ labels:
matcher:
files: '.github/**'
- - label: area/sticky-jest
+ - label: area/app
sync: true
matcher:
- files: 'packages/sticky-jest/**'
-
- - label: area/sticky-prettier
- sync: true
- matcher:
- files: 'packages/sticky-prettier/**'
-
- - label: area/sticky-turbo
- sync: true
- matcher:
- files: 'packages/sticky-turbo/**'
-
- - label: area/sticky-turbo-jest
- sync: true
- matcher:
- files: 'packages/sticky-turbo-jest/**'
-
- - label: area/sticky-typescript
- sync: true
- matcher:
- files: 'packages/sticky-typescript/**'
+ files: 'app/**'
- label: area/eslint-config
sync: true
diff --git a/.github/labels.yml b/.github/labels.yml
index a480046..d6878ce 100644
--- a/.github/labels.yml
+++ b/.github/labels.yml
@@ -43,10 +43,6 @@
name: kind/dependencies
description: Pull requests that update a dependency file
-# Area
-- color: fbca04
- name: area/contracts
-
# Priority
- color: d93f0b
name: priority/urgent-now
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a256c88..1af3661 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -114,3 +114,64 @@ jobs:
with:
working-directory: ./app
command: eas update --branch pr-${{ github.event.number }} --message "Update with commit ${{ github.sha }}"
+
+ test_playwright:
+ name: Test [Playwright]
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ project: [chromium]
+ shardIndex: [1, 2]
+ shardTotal: [2]
+ steps:
+ - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
+ - run: corepack enable yarn
+
+ - uses: actions/setup-node@64ed1c7eab4cce3362f8c340dee64e5eaeef8f7c # v3.6.0
+ with:
+ node-version-file: '.nvmrc'
+ cache: yarn
+
+ - run: yarn install --frozen-lockfile
+
+ - uses: actions/cache/restore@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
+ id: cache-restore
+ with:
+ path: ~/.cache/ms-playwright
+ key: playwright-${{ runner.os }}
+
+ - name: playwright install
+ working-directory: app
+ if: steps.cache-restore.outputs.cache-hit != 'true'
+ run: yarn playwright install --with-deps
+
+ - name: playwright test
+ working-directory: app
+ run: yarn playwright test --project=${{ matrix.project }} --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
+
+ - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2
+ if: always()
+ with:
+ name: app-playwright
+ path: app/playwright/
+ retention-days: 7
+
+ - uses: actions/cache/save@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3.3.1
+ if: always() && steps.cache-restore.outputs.cache-hit != 'true' && matrix.shardIndex == 1
+ with:
+ path: ~/.cache/ms-playwright
+ key: playwright-${{ runner.os }}
+
+ test_completed:
+ name: Test [completed]
+ runs-on: ubuntu-latest
+ if: always()
+ needs:
+ - test_playwright
+ steps:
+ - run: |
+ if ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled') }} ; then
+ exit 1
+ fi
diff --git a/README.md b/README.md
index fd7abe1..67818f4 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,42 @@
[![CI](https://github.com/levaintech/keychain/actions/workflows/ci.yml/badge.svg)](https://github.com/levaintech/keychain/actions/workflows/ci.yml)
+## Testing
+
+### Unit (`*.unit.ts` via jest)
+
+The purpose of unit tests is to test each unit of code in isolation from the rest of the code to quickly pinpoint where
+code is and isn't working as expected. You'll put unit tests in the src directory in accompanying each file with the
+code that they're testing.
+
+They must follow the naming semantic of `*.unit.ts` and placed together in the same directory as the code you're
+testing.
+
+### Integration (`*.i9n.ts` via jest)
+
+Unit tests do have one major disadvantage: even if the units work well in isolation, you do not know if they work well
+together. An integration test takes a small group of units, often two units, and tests their behavior as a whole,
+verifying that they coherently work together.
+
+They must follow the naming semantic of `*.i9n.ts` and placed contextually in the same directory as the context you're
+testing.
+
+### End-to-end (`*.e2e.ts` via playwright)
+
+End-to-end testing verifies that our software works correctly from the beginning to the end of a particular user flow.
+It replicates expected user behavior and various usage scenarios to ensure that your software works as whole. e2e
+testing should mimic (as much as possible) a production equivalent environment and data to simulate real-world
+situations.
+
+They must follow the naming semantic of `*.e2e.ts` and placed contextually in the same directory as the context you're
+testing.
+
+To have a sane end-to-end testing experience, we use [Playwright](https://playwright.dev/) as our main and only e2e
+testing framework. Playwright is built to enable cross-browser web automation that is ever-green, capable, reliable and
+fast. While this isn't a replacement for native mobile testing, it does provide a great way to test our application
+end-to-end. For avoidance of doubt, this means that we will not be using any other e2e testing framework other than
+Playwright.
+
## Security
> While we strive to be as transparent as possible to maintain the integrity of our ecosystem, we also understand that
diff --git a/app/.gitignore b/app/.gitignore
index fa80e13..cd2d416 100644
--- a/app/.gitignore
+++ b/app/.gitignore
@@ -1,2 +1,2 @@
-tailwind.css
-tailwind.json
\ No newline at end of file
+tailwind.json
+playwright/
\ No newline at end of file
diff --git a/app/app/detail.e2e.ts b/app/app/detail.e2e.ts
new file mode 100644
index 0000000..c8317f5
--- /dev/null
+++ b/app/app/detail.e2e.ts
@@ -0,0 +1,10 @@
+import { expect, test } from '@playwright/test';
+
+test.describe('Detail', async () => {
+ test('should go to /', async ({ page, baseURL }) => {
+ await page.goto('/');
+
+ await page.getByTestId('GoToDetail').click();
+ await expect(page.getByTestId('DetailScreen')).toHaveText('Details Screen');
+ });
+});
diff --git a/app/app/detail.tsx b/app/app/detail.tsx
index c0aa7d3..8fa1649 100644
--- a/app/app/detail.tsx
+++ b/app/app/detail.tsx
@@ -10,6 +10,7 @@ export default function Details(): JSX.Element {
{
// Go back to the previous screen using the imperative API.
router.back();
diff --git a/app/app/index.e2e.ts b/app/app/index.e2e.ts
new file mode 100644
index 0000000..310ec21
--- /dev/null
+++ b/app/app/index.e2e.ts
@@ -0,0 +1,9 @@
+import { expect, test } from '@playwright/test';
+
+test.describe('Example', async () => {
+ test('should go to /', async ({ page, baseURL }) => {
+ await page.goto('/');
+ await expect(page).toHaveURL(`${baseURL}/`);
+ await expect(page.getByTestId('KeyChain')).toContainText('KeyChain');
+ });
+});
diff --git a/app/app/index.tsx b/app/app/index.tsx
index 0195624..a6c63f1 100644
--- a/app/app/index.tsx
+++ b/app/app/index.tsx
@@ -8,13 +8,13 @@ export default function Home(): JSX.Element {
return (
-
+
KeyChain — Open up index.tsx to start working on your app!
{/* Use the `Screen` component to configure the layout. */}
{/* Use the `Link` component to enable optimized client-side routing. */}
-
+
Go to Detail
diff --git a/app/metro.config.js b/app/metro.config.js
index 1444077..06e67bb 100644
--- a/app/metro.config.js
+++ b/app/metro.config.js
@@ -13,6 +13,7 @@ const postcss = require('postcss');
*/
module.exports = (async () => {
const config = getDefaultConfig(__dirname);
+ config.resolver.blockList = /^.+\.e2e\.ts$/;
await postCSS(process.env.METRO_CONFIG_JS_WATCH === 'true');
return config;
})();
diff --git a/app/package.json b/app/package.json
index cc9c6fd..87725e5 100644
--- a/app/package.json
+++ b/app/package.json
@@ -6,6 +6,7 @@
"scripts": {
"export": "expo export",
"lint": "eslint .",
+ "playwright:open": "playwright install && playwright test --ui",
"start": "export METRO_CONFIG_JS_WATCH=true && expo start",
"version": "node version.mjs"
},
@@ -46,6 +47,7 @@
"devDependencies": {
"@levain-keychain/eslint-config": "0.0.0",
"@parcel/watcher": "^2.1.0",
+ "@playwright/test": "^1.35.1",
"@types/react": "~18.0.27",
"@types/react-native": "^0.72.2",
"classnames": "^2.3.2",
diff --git a/app/playwright.config.js b/app/playwright.config.js
new file mode 100644
index 0000000..854dd10
--- /dev/null
+++ b/app/playwright.config.js
@@ -0,0 +1,55 @@
+import { defineConfig, devices } from '@playwright/test';
+
+export default defineConfig({
+ testDir: 'app',
+ testMatch: '*.e2e.ts',
+
+ webServer: {
+ command: 'yarn run start',
+ // http://localhost:19000/ isn't reliable to determine if the server is ready.
+ // However, AppEntry.bundle will only be available after the "Web Bundling complete" message.
+ url: 'http://localhost:19000/AppEntry.bundle?platform=web&hot=false',
+ reuseExistingServer: !process.env.CI,
+ timeout: 180 * 1000,
+ stdout: 'pipe',
+ stderr: 'pipe',
+ },
+
+ // Run all tests 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 ? 1 : 0,
+
+ // Opt out of parallel tests on CI.
+ workers: process.env.CI ? 1 : undefined,
+
+ outputDir: './playwright/test-results',
+
+ // Reporter to use
+ reporter: [
+ // For GitHub Actions CI to generate annotations
+ ['github'],
+ ['html', { outputFolder: './playwright/test-report' }],
+ ],
+
+ use: {
+ // Base URL to use in actions like `await page.goto('/')`.
+ baseURL: 'http://localhost:19000',
+ // Collect trace when retrying the failed test.
+ trace: 'on',
+ // equivalent to cypress: macbook-16
+ viewport: { width: 1200, height: 1200 },
+ video: 'on',
+ },
+
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+ ],
+});
diff --git a/packages/eslint-config/index.js b/packages/eslint-config/index.js
index cf58155..00c38cc 100644
--- a/packages/eslint-config/index.js
+++ b/packages/eslint-config/index.js
@@ -1,11 +1,18 @@
module.exports = {
- extends: [
- 'universe',
- 'universe/shared/typescript-analysis',
- '@stickyjs'
- ],
+ extends: ['universe', 'universe/shared/typescript-analysis', '@stickyjs'],
rules: {
'import/no-default-export': 'off',
- '@typescript-eslint/explicit-function-return-type': ['error', {allowExpressions: true}],
- }
-};
\ No newline at end of file
+ '@typescript-eslint/explicit-function-return-type': ['error', { allowExpressions: true }],
+ },
+ overrides: [
+ {
+ files: ['**/*.e2e.ts'],
+ extends: ['prettier', 'plugin:playwright/playwright-test'],
+ rules: {
+ 'unused-imports/no-unused-imports': 'error',
+ 'unused-imports/no-unused-vars': 'off',
+ '@typescript-eslint/explicit-function-return-type': 'off',
+ },
+ },
+ ],
+};
diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json
index e36b074..7b589f5 100644
--- a/packages/eslint-config/package.json
+++ b/packages/eslint-config/package.json
@@ -5,6 +5,7 @@
"main": "index.js",
"dependencies": {
"@stickyjs/eslint-config": "1.2.0",
- "eslint-config-universe": "11.2.0"
+ "eslint-config-universe": "11.2.0",
+ "eslint-plugin-playwright": "^0.12.0"
}
}
diff --git a/yarn.lock b/yarn.lock
index 0dc42ae..6603e35 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1756,6 +1756,16 @@
picocolors "^1.0.0"
tslib "^2.5.0"
+"@playwright/test@^1.35.1":
+ version "1.35.1"
+ resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.35.1.tgz#a596b61e15b980716696f149cc7a2002f003580c"
+ integrity sha512-b5YoFe6J9exsMYg0pQAobNDR85T1nLumUYgUTtKm4d21iX2L7WqKq9dW8NGJ+2vX0etZd+Y7UeuqsxDXm9+5ZA==
+ dependencies:
+ "@types/node" "*"
+ playwright-core "1.35.1"
+ optionalDependencies:
+ fsevents "2.3.2"
+
"@radix-ui/react-compose-refs@1.0.0":
version "1.0.0"
resolved "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz"
@@ -4140,6 +4150,11 @@ eslint-plugin-node@^11.1.0:
resolve "^1.10.1"
semver "^6.1.0"
+eslint-plugin-playwright@^0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-playwright/-/eslint-plugin-playwright-0.12.0.tgz#0c728e07c217b5ea48acef46c52eefba9cf8ebd3"
+ integrity sha512-KXuzQjVzca5irMT/7rvzJKsVDGbQr43oQPc8i+SLEBqmfrTxlwMwRqfv9vtZqh4hpU0jmrnA/EOfwtls+5QC1w==
+
eslint-plugin-prettier@^4.2.1:
version "4.2.1"
resolved "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz"
@@ -4835,7 +4850,7 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
-fsevents@^2.3.2, fsevents@~2.3.2:
+fsevents@2.3.2, fsevents@^2.3.2, fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
@@ -7451,6 +7466,11 @@ pkg-up@^3.1.0:
dependencies:
find-up "^3.0.0"
+playwright-core@1.35.1:
+ version "1.35.1"
+ resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.35.1.tgz#52c1e6ffaa6a8c29de1a5bdf8cce0ce290ffb81d"
+ integrity sha512-pNXb6CQ7OqmGDRspEjlxE49w+4YtR6a3X6mT1hZXeJHWmsEz7SunmvZeiG/+y1yyMZdHnnn73WKYdtV1er0Xyg==
+
plist@^3.0.5:
version "3.0.6"
resolved "https://registry.npmjs.org/plist/-/plist-3.0.6.tgz"