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

@ -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?!
}, },
}); });