Merge pull request 'refactor: Migrate playwright to typescript' (#5734) from anbraten/forgejo:ts-test into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5734
Reviewed-by: Otto <otto@codeberg.org>
Reviewed-by: Michael Kriese <michael.kriese@gmx.de>
This commit is contained in:
Otto 2024-11-11 16:58:29 +00:00
commit 1b3497c9c4
25 changed files with 42 additions and 79 deletions

View file

@ -1,4 +1,4 @@
import {devices} from '@playwright/test'; import {devices, type PlaywrightTestConfig} from '@playwright/test';
const BASE_URL = process.env.GITEA_URL?.replace?.(/\/$/g, '') || 'http://localhost:3000'; const BASE_URL = process.env.GITEA_URL?.replace?.(/\/$/g, '') || 'http://localhost:3000';
@ -8,7 +8,7 @@ const BASE_URL = process.env.GITEA_URL?.replace?.(/\/$/g, '') || 'http://localho
*/ */
export default { export default {
testDir: './tests/e2e/', testDir: './tests/e2e/',
testMatch: /.*\.test\.e2e\.js/, // Match any .test.e2e.js files testMatch: /.*\.test\.e2e\.ts/, // Match any .test.e2e.js files
// you can adjust this value locally to match your machine's power, // you can adjust this value locally to match your machine's power,
// or pass `--workers x` to playwright // or pass `--workers x` to playwright
@ -99,4 +99,4 @@ export default {
outputDir: 'tests/e2e/test-artifacts/', outputDir: 'tests/e2e/test-artifacts/',
/* Folder for test artifacts such as screenshots, videos, traces, etc. */ /* Folder for test artifacts such as screenshots, videos, traces, etc. */
snapshotDir: 'tests/e2e/test-snapshots/', snapshotDir: 'tests/e2e/test-snapshots/',
}; } satisfies PlaywrightTestConfig;

View file

@ -77,7 +77,7 @@ and playwright to perform tests on it.
> (e.g. when only creating new content), > (e.g. when only creating new content),
> or that they restore the initial state for the next browser run. > or that they restore the initial state for the next browser run.
#### With the playwright UI: #### With the playwright UI:
Playwright ships with an integrated UI mode which allows you to Playwright ships with an integrated UI mode which allows you to
run individual tests and to debug them by seeing detailed traces of what playwright does. run individual tests and to debug them by seeing detailed traces of what playwright does.
@ -90,7 +90,7 @@ npx playwright test --ui
#### Running individual tests #### Running individual tests
``` ```
npx playwright test actions.test.e2e.js:9 npx playwright test actions.test.e2e.ts:9
``` ```
First, specify the complete test filename, First, specify the complete test filename,
@ -144,7 +144,7 @@ TEST_PGSQL_HOST=localhost:5432 TEST_PGSQL_DBNAME=test TEST_PGSQL_USERNAME=postgr
### Running individual tests ### Running individual tests
Example command to run `example.test.e2e.js` test file: Example command to run `example.test.e2e.ts` test file:
> **Note** > **Note**
> Unlike integration tests, this filtering is at the file level, not function > Unlike integration tests, this filtering is at the file level, not function
@ -211,7 +211,7 @@ Feel free to improve the logic used there if you need more advanced functionalit
If you can, perform automated accessibility testing using If you can, perform automated accessibility testing using
[AxeCore](https://github.com/dequelabs/axe-core-npm/blob/develop/packages/playwright/README.md). [AxeCore](https://github.com/dequelabs/axe-core-npm/blob/develop/packages/playwright/README.md).
Take a look at `shared/forms.js` and some other places for inspiration. Take a look at `shared/forms.ts` and some other places for inspiration.
### List related files coverage ### List related files coverage

View file

@ -1,5 +1,3 @@
// @ts-check
// @watch start // @watch start
// templates/repo/actions/** // templates/repo/actions/**
// web_src/css/actions.css // web_src/css/actions.css
@ -12,7 +10,7 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, load_logged_in_context} from './utils_e2e.js'; import {test, login_user, load_logged_in_context} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -29,7 +29,7 @@ func initChangedFiles() {
globalPatterns := []string{ globalPatterns := []string{
// meta and config // meta and config
"Makefile", "Makefile",
"playwright.config.js", "playwright.config.ts",
".forgejo/workflows/testing.yml", ".forgejo/workflows/testing.yml",
"tests/e2e/*.go", "tests/e2e/*.go",
"tests/e2e/shared/*", "tests/e2e/shared/*",

View file

@ -1,11 +1,9 @@
// @ts-check
// @watch start // @watch start
// web_src/js/components/DashboardRepoList.vue // web_src/js/components/DashboardRepoList.vue
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, load_logged_in_context} from './utils_e2e.js'; import {test, login_user, load_logged_in_context} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -79,7 +79,7 @@ func TestMain(m *testing.M) {
// TestE2e should be the only test e2e necessary. It will collect all "*.test.e2e.js" files in this directory and build a test for each. // TestE2e should be the only test e2e necessary. It will collect all "*.test.e2e.js" files in this directory and build a test for each.
func TestE2e(t *testing.T) { func TestE2e(t *testing.T) {
// Find the paths of all e2e test files in test directory. // Find the paths of all e2e test files in test directory.
searchGlob := filepath.Join(filepath.Dir(setting.AppPath), "tests", "e2e", "*.test.e2e.js") searchGlob := filepath.Join(filepath.Dir(setting.AppPath), "tests", "e2e", "*.test.e2e.ts")
paths, err := filepath.Glob(searchGlob) paths, err := filepath.Glob(searchGlob)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View file

@ -1,5 +1,3 @@
// @ts-check
// @watch start // @watch start
// templates/user/auth/** // templates/user/auth/**
// web_src/js/features/user-** // web_src/js/features/user-**
@ -7,7 +5,7 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, save_visual} from './utils_e2e.js'; import {test, login_user, save_visual} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -1,4 +1,3 @@
// @ts-check
// document is a global in evaluate, so it's safe to ignore here // document is a global in evaluate, so it's safe to ignore here
// eslint playwright/no-conditional-in-test: 0 // eslint playwright/no-conditional-in-test: 0
@ -8,7 +7,7 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test} from './utils_e2e.js'; import {test} from './utils_e2e.ts';
test('Explore view taborder', async ({page}) => { test('Explore view taborder', async ({page}) => {
await page.goto('/explore/repos'); await page.goto('/explore/repos');

View file

@ -1,5 +1,3 @@
// @ts-check
// @watch start // @watch start
// web_src/js/features/comp/** // web_src/js/features/comp/**
// web_src/js/features/repo-** // web_src/js/features/repo-**
@ -7,7 +5,7 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, login} from './utils_e2e.js'; import {test, login_user, login} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -1,5 +1,3 @@
// @ts-check
// @watch start // @watch start
// templates/repo/issue/view_content/** // templates/repo/issue/view_content/**
// web_src/css/repo/issue-** // web_src/css/repo/issue-**
@ -7,7 +5,7 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, login} from './utils_e2e.js'; import {test, login_user, login} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -1,5 +1,3 @@
// @ts-check
// @watch start // @watch start
// web_src/js/features/comp/ComboMarkdownEditor.js // web_src/js/features/comp/ComboMarkdownEditor.js
// web_src/css/editor/combomarkdowneditor.css // web_src/css/editor/combomarkdowneditor.css
@ -7,7 +5,7 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, load_logged_in_context, login_user} from './utils_e2e.js'; import {test, load_logged_in_context, login_user} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -1,11 +1,9 @@
// @ts-check
// @watch start // @watch start
// web_src/css/markup/** // web_src/css/markup/**
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test} from './utils_e2e.js'; import {test} from './utils_e2e.ts';
test('markup with #xyz-mode-only', async ({page}) => { test('markup with #xyz-mode-only', async ({page}) => {
const response = await page.goto('/user2/repo1/issues/1'); const response = await page.goto('/user2/repo1/issues/1');

View file

@ -1,5 +1,3 @@
// @ts-check
// @watch start // @watch start
// templates/org/team/new.tmpl // templates/org/team/new.tmpl
// web_src/css/form.css // web_src/css/form.css
@ -7,8 +5,8 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, login} from './utils_e2e.js'; import {test, login_user, login} from './utils_e2e.ts';
import {validate_form} from './shared/forms.js'; import {validate_form} from './shared/forms.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -1,5 +1,3 @@
// @ts-check
// @watch start // @watch start
// routers/web/user/** // routers/web/user/**
// templates/shared/user/** // templates/shared/user/**
@ -7,7 +5,7 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, load_logged_in_context} from './utils_e2e.js'; import {test, login_user, load_logged_in_context} from './utils_e2e.ts';
test('Follow actions', async ({browser}, workerInfo) => { test('Follow actions', async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -1,12 +1,10 @@
// @ts-check
// @watch start // @watch start
// web_src/js/features/comp/ReactionSelector.js // web_src/js/features/comp/ReactionSelector.js
// routers/web/repo/issue.go // routers/web/repo/issue.go
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, load_logged_in_context} from './utils_e2e.js'; import {test, login_user, load_logged_in_context} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -1,5 +1,3 @@
// @ts-check
// @watch start // @watch start
// models/repo/attachment.go // models/repo/attachment.go
// modules/structs/attachment.go // modules/structs/attachment.go
@ -11,8 +9,8 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, save_visual, load_logged_in_context} from './utils_e2e.js'; import {test, login_user, save_visual, load_logged_in_context} from './utils_e2e.ts';
import {validate_form} from './shared/forms.js'; import {validate_form} from './shared/forms.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -1,5 +1,3 @@
// @ts-check
// @watch start // @watch start
// web_src/js/features/repo-code.js // web_src/js/features/repo-code.js
// web_src/css/repo.css // web_src/css/repo.css
@ -7,7 +5,7 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, load_logged_in_context} from './utils_e2e.js'; import {test, login_user, load_logged_in_context} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -1,5 +1,3 @@
// @ts-check
// @watch start // @watch start
// templates/repo/graph.tmpl // templates/repo/graph.tmpl
// web_src/css/features/gitgraph.css // web_src/css/features/gitgraph.css
@ -7,7 +5,7 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, load_logged_in_context} from './utils_e2e.js'; import {test, login_user, load_logged_in_context} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -1,11 +1,9 @@
// @ts-check
// @watch start // @watch start
// web_src/js/features/repo-migrate.js // web_src/js/features/repo-migrate.js
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, load_logged_in_context} from './utils_e2e.js'; import {test, login_user, load_logged_in_context} from './utils_e2e.ts';
test.beforeAll(({browser}, workerInfo) => login_user(browser, workerInfo, 'user2')); test.beforeAll(({browser}, workerInfo) => login_user(browser, workerInfo, 'user2'));

View file

@ -1,5 +1,3 @@
// @ts-check
// @watch start // @watch start
// templates/webhook/shared-settings.tmpl // templates/webhook/shared-settings.tmpl
// templates/repo/settings/** // templates/repo/settings/**
@ -9,8 +7,8 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, login} from './utils_e2e.js'; import {test, login_user, login} from './utils_e2e.ts';
import {validate_form} from './shared/forms.js'; import {validate_form} from './shared/forms.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -1,12 +1,10 @@
// @ts-check
// @watch start // @watch start
// templates/repo/wiki/** // templates/repo/wiki/**
// web_src/css/repo** // web_src/css/repo**
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test} from './utils_e2e.js'; import {test} from './utils_e2e.ts';
test(`Search for long titles and test for no overflow`, async ({page}, workerInfo) => { test(`Search for long titles and test for no overflow`, async ({page}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Fails as always, see https://codeberg.org/forgejo/forgejo/pulls/5326#issuecomment-2313275'); test.skip(workerInfo.project.name === 'Mobile Safari', 'Fails as always, see https://codeberg.org/forgejo/forgejo/pulls/5326#issuecomment-2313275');

View file

@ -1,5 +1,3 @@
// @ts-check
// @watch start // @watch start
// templates/org/** // templates/org/**
// templates/repo/** // templates/repo/**
@ -7,7 +5,7 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, load_logged_in_context} from './utils_e2e.js'; import {test, login_user, load_logged_in_context} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');

View file

@ -1,8 +1,7 @@
import {expect} from '@playwright/test'; import {expect, type Page} from '@playwright/test';
import {AxeBuilder} from '@axe-core/playwright'; import {AxeBuilder} from '@axe-core/playwright';
export async function validate_form({page}, scope) { export async function validate_form({page}: {page: Page}, scope: 'form' | 'fieldset' = 'form') {
scope ??= 'form';
const accessibilityScanResults = await new AxeBuilder({page}) const accessibilityScanResults = await new AxeBuilder({page})
// disable checking for link style - should be fixed, but not now // disable checking for link style - should be fixed, but not now
.disableRules('link-in-text-block') .disableRules('link-in-text-block')

View file

@ -1,4 +1,4 @@
import {expect, test as baseTest} from '@playwright/test'; import {expect, test as baseTest, type Browser, type BrowserContextOptions, type APIRequestContext, type TestInfo, type Page} from '@playwright/test';
export const test = baseTest.extend({ export const test = baseTest.extend({
context: async ({browser}, use) => { context: async ({browser}, use) => {
@ -6,7 +6,7 @@ export const test = baseTest.extend({
}, },
}); });
async function test_context(browser, options) { async function test_context(browser: Browser, options?: BrowserContextOptions) {
const context = await browser.newContext(options); const context = await browser.newContext(options);
context.on('page', (page) => { context.on('page', (page) => {
@ -21,7 +21,7 @@ const LOGIN_PASSWORD = 'password';
// log in user and store session info. This should generally be // log in user and store session info. This should generally be
// run in test.beforeAll(), then the session can be loaded in tests. // run in test.beforeAll(), then the session can be loaded in tests.
export async function login_user(browser, workerInfo, user) { export async function login_user(browser: Browser, workerInfo: TestInfo, user: string) {
test.setTimeout(60000); test.setTimeout(60000);
// Set up a new context // Set up a new context
const context = await test_context(browser); const context = await test_context(browser);
@ -47,7 +47,7 @@ export async function login_user(browser, workerInfo, user) {
return context; return context;
} }
export async function load_logged_in_context(browser, workerInfo, user) { export async function load_logged_in_context(browser: Browser, workerInfo: TestInfo, user: string) {
let context; let context;
try { try {
context = await test_context(browser, {storageState: `${ARTIFACTS_PATH}/state-${user}-${workerInfo.workerIndex}.json`}); context = await test_context(browser, {storageState: `${ARTIFACTS_PATH}/state-${user}-${workerInfo.workerIndex}.json`});
@ -59,12 +59,12 @@ export async function load_logged_in_context(browser, workerInfo, user) {
return context; return context;
} }
export async function login({browser}, workerInfo) { export async function login({browser}: {browser: Browser}, workerInfo: TestInfo) {
const context = await load_logged_in_context(browser, workerInfo, 'user2'); const context = await load_logged_in_context(browser, workerInfo, 'user2');
return await context.newPage(); return await context?.newPage();
} }
export async function save_visual(page) { export async function save_visual(page: Page) {
// Optionally include visual testing // Optionally include visual testing
if (process.env.VISUAL_TEST) { if (process.env.VISUAL_TEST) {
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
@ -83,7 +83,7 @@ export async function save_visual(page) {
// Create a temporary user and login to that user and store session info. // Create a temporary user and login to that user and store session info.
// This should ideally run on a per test basis. // This should ideally run on a per test basis.
export async function create_temp_user(browser, workerInfo, request) { export async function create_temp_user(browser: Browser, workerInfo: TestInfo, request: APIRequestContext) {
const username = globalThis.crypto.randomUUID(); const username = globalThis.crypto.randomUUID();
const newUser = await request.post(`/api/v1/admin/users`, { const newUser = await request.post(`/api/v1/admin/users`, {
headers: { headers: {

View file

@ -1,6 +1,5 @@
// Copyright 2024 The Forgejo Authors. All rights reserved. // Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
// @ts-check
// @watch start // @watch start
// templates/user/auth/** // templates/user/auth/**
@ -9,7 +8,7 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, create_temp_user} from './utils_e2e.js'; import {test, create_temp_user} from './utils_e2e.ts';
test('WebAuthn register & login flow', async ({browser, request}, workerInfo) => { test('WebAuthn register & login flow', async ({browser, request}, workerInfo) => {
test.skip(workerInfo.project.name !== 'chromium', 'Uses Chrome protocol'); test.skip(workerInfo.project.name !== 'chromium', 'Uses Chrome protocol');
@ -31,7 +30,7 @@ test('WebAuthn register & login flow', async ({browser, request}, workerInfo) =>
transport: 'usb', transport: 'usb',
automaticPresenceSimulation: true, automaticPresenceSimulation: true,
isUserVerified: true, isUserVerified: true,
backupEligibility: true, backupEligibility: true, // TODO: this doesn't seem to be available?!
}, },
}); });