feat(test): added e2e tests

This commit is contained in:
Corentin Thomasset 2023-04-09 17:59:57 +02:00 committed by Corentin THOMASSET
parent ebfdb64fde
commit ec7cb9351c
11 changed files with 245 additions and 5 deletions

23
.github/workflows/playwright.yml vendored Normal file
View file

@ -0,0 +1,23 @@
name: E2E tests
on:
pull_request:
push:
branches:
- main
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: corepack enable
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'pnpm'
- name: Install dependencies
run: pnpm install
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
- name: Run Playwright tests
run: pnpm exec playwright test

5
.gitignore vendored
View file

@ -27,4 +27,7 @@ coverage
*.sln
*.sw?
.env
.env
/test-results/
/playwright-report/
/playwright/.cache/

1
components.d.ts vendored
View file

@ -25,7 +25,6 @@ declare module '@vue/runtime-core' {
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NCopyableInput: typeof import('naive-ui')['NCopyableInput']
NDatePicker: typeof import('naive-ui')['NDatePicker']
NDivider: typeof import('naive-ui')['NDivider']
NDynamicInput: typeof import('naive-ui')['NDynamicInput']

View file

@ -25,6 +25,7 @@
"preview": "vite preview --port 5050",
"test": "npm run test:unit",
"test:unit": "vitest --environment jsdom",
"test:e2e": "playwright test",
"coverage": "vitest run --coverage",
"typecheck": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
"lint": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore",
@ -75,6 +76,7 @@
"vue-router": "^4.1.6"
},
"devDependencies": {
"@playwright/test": "^1.32.2",
"@rushstack/eslint-patch": "^1.2.0",
"@types/bcryptjs": "^2.4.2",
"@types/crypto-js": "^4.1.1",

80
playwright.config.ts Normal file
View file

@ -0,0 +1,80 @@
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: './src',
testMatch: /.*\.e2e\.(spec\.)?ts/,
/* 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.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: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: 'http://127.0.0.1:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
testIdAttribute: 'data-test-id',
},
/* 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: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ..devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
webServer: {
command: 'npm run dev',
url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},
});

20
pnpm-lock.yaml generated
View file

@ -126,6 +126,9 @@ dependencies:
version: 4.1.6(vue@3.2.47)
devDependencies:
'@playwright/test':
specifier: ^1.32.2
version: 1.32.2
'@rushstack/eslint-patch':
specifier: ^1.2.0
version: 1.2.0
@ -1721,6 +1724,17 @@ packages:
tslib: 2.5.0
dev: true
/@playwright/test@1.32.2:
resolution: {integrity: sha512-nhaTSDpEdTTttdkDE8Z6K3icuG1DVRxrl98Qq0Lfc63SS9a2sjc9+x8ezysh7MzCKz6Y+nArml3/mmt+gqRmQQ==}
engines: {node: '>=14'}
hasBin: true
dependencies:
'@types/node': 16.18.22
playwright-core: 1.32.2
optionalDependencies:
fsevents: 2.3.2
dev: true
/@polka/url@1.0.0-next.21:
resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==}
dev: true
@ -6800,6 +6814,12 @@ packages:
engines: {node: '>=10'}
dev: false
/playwright-core@1.32.2:
resolution: {integrity: sha512-zD7aonO+07kOTthsrCR3YCVnDcqSHIJpdFUtZEMOb6//1Rc7/6mZDRdw+nlzcQiQltOOsiqI3rrSyn/SlyjnJQ==}
engines: {node: '>=14'}
hasBin: true
dev: true
/pngjs@5.0.0:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}

View file

@ -5,6 +5,7 @@ import { fileURLToPath } from 'url';
const currentDirname = dirname(fileURLToPath(import.meta.url));
const toolsDir = join(currentDirname, '..', 'src', 'tools');
// eslint-disable-next-line no-undef
const toolName = process.argv[2];
if (!toolName) {
@ -73,6 +74,28 @@ import { expect, describe, it } from 'vitest';
`,
);
createToolFile(
`${toolName}.e2e.spec.ts`,
`
import { test, expect } from '@playwright/test';
test.describe('Tool - ${toolNameTitleCase}', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/${toolName}');
});
test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle('${toolNameTitleCase} - IT Tools');
});
test('', async ({ page }) => {
});
});
`,
);
const toolsIndex = join(toolsDir, 'index.ts');
const indexContent = await readFile(toolsIndex, { encoding: 'utf-8' }).then((r) => r.split('\n'));

View file

@ -0,0 +1,48 @@
import { test, expect } from '@playwright/test';
test.describe('Tool - OTP code generator', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/otp-generator');
});
test('Has title', async ({ page }) => {
await expect(page).toHaveTitle('OTP code generator - IT Tools');
});
test('Secret hexa value is computed from provided secret', async ({ page }) => {
await page.getByPlaceholder('Paste your TOTP secret...').fill('ITTOOLS');
const secretInHex = await page.getByPlaceholder('Secret in hex will be displayed here').inputValue();
expect(secretInHex).toEqual('44e6e72e02');
});
test('OTP a generated from the provided secret', async ({ page }) => {
page.evaluate(() => {
Date.now = () => 1609477200000; //Jan 1, 2021
});
await page.getByPlaceholder('Paste your TOTP secret...').fill('ITTOOLS');
const previousOtp = await page.getByTestId('previous-otp').innerText();
const currentOtp = await page.getByTestId('current-otp').innerText();
const nextOtp = await page.getByTestId('next-otp').innerText();
expect(previousOtp.trim()).toEqual('028034');
expect(currentOtp.trim()).toEqual('162195');
expect(nextOtp.trim()).toEqual('452815');
});
test('You can generate a new random secret', async ({ page }) => {
const initialSecret = await page.getByPlaceholder('Paste your TOTP secret...').inputValue();
await page
.locator('div')
.filter({ hasText: /^Secret$/ })
.getByRole('button')
.click();
const newSecret = await page.getByPlaceholder('Paste your TOTP secret...').inputValue();
expect(newSecret).not.toEqual(initialSecret);
});
});

View file

@ -8,13 +8,21 @@
<n-input-group>
<n-tooltip trigger="hover" placement="bottom">
<template #trigger>
<n-button secondary @click.prevent="copyPrevious(tokens.previous)">{{ tokens.previous }}</n-button>
<n-button data-test-id="previous-otp" secondary @click.prevent="copyPrevious(tokens.previous)">{{
tokens.previous
}}</n-button>
</template>
<div>{{ previousCopied ? 'Copied !' : 'Copy previous OTP' }}</div>
</n-tooltip>
<n-tooltip trigger="hover" placement="bottom">
<template #trigger>
<n-button tertiary type="primary" class="current-otp" @click.prevent="copyCurrent(tokens.current)">
<n-button
tertiary
type="primary"
data-test-id="current-otp"
class="current-otp"
@click.prevent="copyCurrent(tokens.current)"
>
{{ tokens.current }}
</n-button>
</template>
@ -22,7 +30,9 @@
</n-tooltip>
<n-tooltip trigger="hover" placement="bottom">
<template #trigger>
<n-button secondary @click.prevent="copyNext(tokens.next)">{{ tokens.next }}</n-button>
<n-button secondary data-test-id="next-otp" @click.prevent="copyNext(tokens.next)">{{
tokens.next
}}</n-button>
</template>
<div>{{ nextCopied ? 'Copied !' : 'Copy next OTP' }}</div>
</n-tooltip>

View file

@ -0,0 +1,19 @@
import { test, expect } from '@playwright/test';
test.describe('Tool - Token generator', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/token-generator');
});
test('Has title', async ({ page }) => {
await expect(page).toHaveTitle('Token generator - IT Tools');
});
test('New token on refresh', async ({ page }) => {
const initialToken = await page.getByPlaceholder('The token...').inputValue();
await page.getByRole('button', { name: 'Refresh' }).click();
const newToken = await page.getByPlaceholder('The token...').inputValue();
expect(newToken).not.toEqual(initialToken);
});
});

13
vitest.config.ts Normal file
View file

@ -0,0 +1,13 @@
import { configDefaults, defineConfig } from 'vitest/config';
import path from 'path';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
test: {
exclude: [...configDefaults.exclude, '**/*.e2e.spec.ts'],
},
});