Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
feat(app): setup playwright testing (#52)
Browse files Browse the repository at this point in the history
#### What this PR does / why we need it:

For e2e testing.
  • Loading branch information
fuxingloh authored Jun 28, 2023
1 parent 9a72969 commit 7dfa3b5
Show file tree
Hide file tree
Showing 15 changed files with 219 additions and 40 deletions.
24 changes: 2 additions & 22 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 0 additions & 4 deletions .github/labels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
61 changes: 61 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions app/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
tailwind.css
tailwind.json
tailwind.json
playwright/
10 changes: 10 additions & 0 deletions app/app/detail.e2e.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
1 change: 1 addition & 0 deletions app/app/detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default function Details(): JSX.Element {
<Stack.Screen options={{ title: 'Detail' }} />
<Text
style={tailwind('text-2xl font-medium')}
testID="DetailScreen"
onPress={() => {
// Go back to the previous screen using the imperative API.
router.back();
Expand Down
9 changes: 9 additions & 0 deletions app/app/index.e2e.ts
Original file line number Diff line number Diff line change
@@ -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');
});
});
4 changes: 2 additions & 2 deletions app/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ export default function Home(): JSX.Element {

return (
<View style={tailwind('flex-1 items-center justify-center bg-white')}>
<Text style={tailwind('text-center text-xl text-blue-600')}>
<Text style={tailwind('text-center text-xl text-blue-600')} testID="KeyChain">
KeyChain — Open up index.tsx to start working on your app!
</Text>
{/* Use the `Screen` component to configure the layout. */}
<Stack.Screen options={{ title: 'Overview' }} />
{/* Use the `Link` component to enable optimized client-side routing. */}
<Link href="/detail" style={tailwind('my-2')}>
<Link href="/detail" style={tailwind('my-2')} testID="GoToDetail">
<View style={tailwind('rounded bg-amber-300 px-4 py-1')}>
<Text style={tailwind('text-xl')}>Go to Detail</Text>
</View>
Expand Down
1 change: 1 addition & 0 deletions app/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
})();
Expand Down
2 changes: 2 additions & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down Expand Up @@ -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",
Expand Down
55 changes: 55 additions & 0 deletions app/playwright.config.js
Original file line number Diff line number Diff line change
@@ -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'] },
},
],
});
23 changes: 15 additions & 8 deletions packages/eslint-config/index.js
Original file line number Diff line number Diff line change
@@ -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}],
}
};
'@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',
},
},
],
};
3 changes: 2 additions & 1 deletion packages/eslint-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
22 changes: 21 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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==
Expand Down Expand Up @@ -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"
Expand Down

0 comments on commit 7dfa3b5

Please sign in to comment.