diff --git a/tests/e2e/actions.test.e2e.ts b/tests/e2e/actions.test.e2e.ts
index 0aa1c747dc..e9f7db52f2 100644
--- a/tests/e2e/actions.test.e2e.ts
+++ b/tests/e2e/actions.test.e2e.ts
@@ -20,7 +20,6 @@ const workflow_trigger_notification_text = 'This workflow has a workflow_dispatc
 
 test('workflow dispatch present', async ({browser}, workerInfo) => {
   const context = await load_logged_in_context(browser, workerInfo, 'user2');
-  /** @type {import('@playwright/test').Page} */
   const page = await context.newPage();
 
   await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0');
@@ -40,7 +39,6 @@ test('workflow dispatch error: missing inputs', async ({browser}, workerInfo) =>
   test.skip(workerInfo.project.name === 'Mobile Safari', 'Flaky behaviour on mobile safari; see https://codeberg.org/forgejo/forgejo/pulls/3334#issuecomment-2033383');
 
   const context = await load_logged_in_context(browser, workerInfo, 'user2');
-  /** @type {import('@playwright/test').Page} */
   const page = await context.newPage();
 
   await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0');
@@ -62,14 +60,13 @@ test('workflow dispatch success', async ({browser}, workerInfo) => {
   test.skip(workerInfo.project.name === 'Mobile Safari', 'Flaky behaviour on mobile safari; see https://codeberg.org/forgejo/forgejo/pulls/3334#issuecomment-2033383');
 
   const context = await load_logged_in_context(browser, workerInfo, 'user2');
-  /** @type {import('@playwright/test').Page} */
   const page = await context.newPage();
 
   await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0');
 
   await page.locator('#workflow_dispatch_dropdown>button').click();
 
-  await page.type('input[name="inputs[string2]"]', 'abc');
+  await page.fill('input[name="inputs[string2]"]', 'abc');
   await page.locator('#workflow-dispatch-submit').click();
 
   await expect(page.getByText('Workflow run was successfully requested.')).toBeVisible();
diff --git a/tests/e2e/example.test.e2e.ts b/tests/e2e/example.test.e2e.ts
index 90fd9169a4..64818c4557 100644
--- a/tests/e2e/example.test.e2e.ts
+++ b/tests/e2e/example.test.e2e.ts
@@ -21,10 +21,10 @@ test('Load Homepage', async ({page}) => {
 test('Register Form', async ({page}, workerInfo) => {
   const response = await page.goto('/user/sign_up');
   expect(response?.status()).toBe(200); // Status OK
-  await page.type('input[name=user_name]', `e2e-test-${workerInfo.workerIndex}`);
-  await page.type('input[name=email]', `e2e-test-${workerInfo.workerIndex}@test.com`);
-  await page.type('input[name=password]', 'test123test123');
-  await page.type('input[name=retype]', 'test123test123');
+  await page.fill('input[name=user_name]', `e2e-test-${workerInfo.workerIndex}`);
+  await page.fill('input[name=email]', `e2e-test-${workerInfo.workerIndex}@test.com`);
+  await page.fill('input[name=password]', 'test123test123');
+  await page.fill('input[name=retype]', 'test123test123');
   await page.click('form button.ui.primary.button:visible');
   // Make sure we routed to the home page. Else login failed.
   expect(page.url()).toBe(`${workerInfo.project.use.baseURL}/`);
diff --git a/tests/e2e/issue-sidebar.test.e2e.ts b/tests/e2e/issue-sidebar.test.e2e.ts
index 422f3ef94e..1e05069e7f 100644
--- a/tests/e2e/issue-sidebar.test.e2e.ts
+++ b/tests/e2e/issue-sidebar.test.e2e.ts
@@ -4,19 +4,18 @@
 // web_src/js/features/repo-issue**
 // @watch end
 
-import {expect} from '@playwright/test';
+/* eslint playwright/expect-expect: ["error", { "assertFunctionNames": ["check_wip"] }] */
+
+import {expect, type Page} from '@playwright/test';
 import {test, login_user, login} from './utils_e2e.ts';
 
 test.beforeAll(async ({browser}, workerInfo) => {
   await login_user(browser, workerInfo, 'user2');
 });
 
-/* eslint-disable playwright/expect-expect */
-// some tests are reported to have no assertions,
-// which is not correct, because they use the global helper function
 test.describe('Pull: Toggle WIP', () => {
   const prTitle = 'pull5';
-  async function toggle_wip_to({page}, should) {
+  async function toggle_wip_to({page}, should: boolean) {
     await page.waitForLoadState('domcontentloaded');
     if (should) {
       await page.getByText('Still in progress?').click();
@@ -25,7 +24,7 @@ test.describe('Pull: Toggle WIP', () => {
     }
   }
 
-  async function check_wip({page}, is) {
+  async function check_wip({page}, is: boolean) {
     const elemTitle = 'h1';
     const stateLabel = '.issue-state-label';
     await page.waitForLoadState('domcontentloaded');
@@ -96,12 +95,11 @@ test.describe('Pull: Toggle WIP', () => {
     await expect(page.locator('h1')).toContainText(maxLenStr);
   });
 });
-/* eslint-enable playwright/expect-expect */
 
 test('Issue: Labels', async ({browser}, workerInfo) => {
   test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636');
 
-  async function submitLabels({page}) {
+  async function submitLabels({page}: {page: Page}) {
     const submitted = page.waitForResponse('/user2/repo1/issues/labels');
     await page.locator('textarea').first().click(); // close via unrelated element
     await submitted;
@@ -199,7 +197,7 @@ test('New Issue: Assignees', async ({browser}, workerInfo) => {
 
   // Assign other user (with searchbox)
   await page.locator('.select-assignees.dropdown').click();
-  await page.type('.select-assignees .menu .search input', 'user4');
+  await page.fill('.select-assignees .menu .search input', 'user4');
   await expect(page.locator('.select-assignees .menu .item').filter({hasText: 'user2'})).toBeHidden();
   await expect(page.locator('.select-assignees .menu .item').filter({hasText: 'user4'})).toBeVisible();
   await page.locator('.select-assignees .menu .item').filter({hasText: 'user4'}).click();
diff --git a/tests/e2e/markdown-editor.test.e2e.ts b/tests/e2e/markdown-editor.test.e2e.ts
index 5db242bb36..4cdf4644f7 100644
--- a/tests/e2e/markdown-editor.test.e2e.ts
+++ b/tests/e2e/markdown-editor.test.e2e.ts
@@ -29,7 +29,7 @@ test('markdown indentation', async ({browser}, workerInfo) => {
 
   // Indent, then unindent first line
   await textarea.focus();
-  await textarea.evaluate((it) => it.setSelectionRange(0, 0));
+  await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(0, 0));
   await indent.click();
   await expect(textarea).toHaveValue(`${tab}* first\n* second\n* third\n* last`);
   await unindent.click();
@@ -45,7 +45,7 @@ test('markdown indentation', async ({browser}, workerInfo) => {
 
   // Subsequently, select a chunk of 2nd and 3rd line and indent both, preserving the cursor position in relation to text
   await textarea.focus();
-  await textarea.evaluate((it) => it.setSelectionRange(it.value.indexOf('cond'), it.value.indexOf('hird')));
+  await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('cond'), it.value.indexOf('hird')));
   await indent.click();
   const lines23 = `* first\n${tab}${tab}* second\n${tab}* third\n* last`;
   await expect(textarea).toHaveValue(lines23);
@@ -60,7 +60,7 @@ test('markdown indentation', async ({browser}, workerInfo) => {
 
   // Indent and unindent with cursor at the end of the line
   await textarea.focus();
-  await textarea.evaluate((it) => it.setSelectionRange(it.value.indexOf('cond'), it.value.indexOf('cond')));
+  await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('cond'), it.value.indexOf('cond')));
   await textarea.press('End');
   await indent.click();
   await expect(textarea).toHaveValue(`* first\n${tab}* second\n* third\n* last`);
@@ -69,7 +69,7 @@ test('markdown indentation', async ({browser}, workerInfo) => {
 
   // Check that Tab does work after input
   await textarea.focus();
-  await textarea.evaluate((it) => it.setSelectionRange(it.value.length, it.value.length));
+  await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
   await textarea.press('Shift+Enter'); // Avoid triggering the prefix continuation feature
   await textarea.pressSequentially('* least');
   await indent.click();
@@ -78,7 +78,7 @@ test('markdown indentation', async ({browser}, workerInfo) => {
   // Check that partial indents are cleared
   await textarea.focus();
   await textarea.fill(initText);
-  await textarea.evaluate((it) => it.setSelectionRange(it.value.indexOf('* second'), it.value.indexOf('* second')));
+  await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('* second'), it.value.indexOf('* second')));
   await textarea.pressSequentially('  ');
   await unindent.click();
   await expect(textarea).toHaveValue(initText);
@@ -99,7 +99,7 @@ test('markdown list continuation', async ({browser}, workerInfo) => {
   await textarea.fill(initText);
 
   // Test continuation of '* ' prefix
-  await textarea.evaluate((it) => it.setSelectionRange(it.value.indexOf('cond'), it.value.indexOf('cond')));
+  await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.indexOf('cond'), it.value.indexOf('cond')));
   await textarea.press('End');
   await textarea.press('Enter');
   await textarea.pressSequentially('middle');
@@ -112,7 +112,7 @@ test('markdown list continuation', async ({browser}, workerInfo) => {
   await expect(textarea).toHaveValue(`* first\n* second\n${tab}* middle\n${tab}* muddle\n* third\n* last`);
 
   // Test breaking in the middle of a line
-  await textarea.evaluate((it) => it.setSelectionRange(it.value.lastIndexOf('ddle'), it.value.lastIndexOf('ddle')));
+  await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.lastIndexOf('ddle'), it.value.lastIndexOf('ddle')));
   await textarea.pressSequentially('tate');
   await textarea.press('Enter');
   await textarea.pressSequentially('me');
@@ -120,7 +120,7 @@ test('markdown list continuation', async ({browser}, workerInfo) => {
 
   // Test not triggering when Shift held
   await textarea.fill(initText);
-  await textarea.evaluate((it) => it.setSelectionRange(it.value.length, it.value.length));
+  await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
   await textarea.press('Shift+Enter');
   await textarea.press('Enter');
   await textarea.pressSequentially('...but not least');
@@ -128,28 +128,28 @@ test('markdown list continuation', async ({browser}, workerInfo) => {
 
   // Test continuation of ordered list
   await textarea.fill(`1. one\n2. two`);
-  await textarea.evaluate((it) => it.setSelectionRange(it.value.length, it.value.length));
+  await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
   await textarea.press('Enter');
   await textarea.pressSequentially('three');
   await expect(textarea).toHaveValue(`1. one\n2. two\n3. three`);
 
   // Test continuation of alternative ordered list syntax
   await textarea.fill(`1) one\n2) two`);
-  await textarea.evaluate((it) => it.setSelectionRange(it.value.length, it.value.length));
+  await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
   await textarea.press('Enter');
   await textarea.pressSequentially('three');
   await expect(textarea).toHaveValue(`1) one\n2) two\n3) three`);
 
   // Test continuation of blockquote
   await textarea.fill(`> knowledge is power`);
-  await textarea.evaluate((it) => it.setSelectionRange(it.value.length, it.value.length));
+  await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
   await textarea.press('Enter');
   await textarea.pressSequentially('france is bacon');
   await expect(textarea).toHaveValue(`> knowledge is power\n> france is bacon`);
 
   // Test continuation of checklists
   await textarea.fill(`- [ ] have a problem\n- [x] create a solution`);
-  await textarea.evaluate((it) => it.setSelectionRange(it.value.length, it.value.length));
+  await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
   await textarea.press('Enter');
   await textarea.pressSequentially('write a test');
   await expect(textarea).toHaveValue(`- [ ] have a problem\n- [x] create a solution\n- [ ] write a test`);
@@ -174,7 +174,7 @@ test('markdown list continuation', async ({browser}, workerInfo) => {
   ];
   for (const prefix of prefixes) {
     await textarea.fill(`${prefix}one`);
-    await textarea.evaluate((it) => it.setSelectionRange(it.value.length, it.value.length));
+    await textarea.evaluate((it:HTMLTextAreaElement) => it.setSelectionRange(it.value.length, it.value.length));
     await textarea.press('Enter');
     await textarea.pressSequentially('two');
     await expect(textarea).toHaveValue(`${prefix}one\n${prefix}two`);
diff --git a/tests/e2e/reaction-selectors.test.e2e.ts b/tests/e2e/reaction-selectors.test.e2e.ts
index a52b47e036..3bd54c7881 100644
--- a/tests/e2e/reaction-selectors.test.e2e.ts
+++ b/tests/e2e/reaction-selectors.test.e2e.ts
@@ -3,14 +3,14 @@
 // routers/web/repo/issue.go
 // @watch end
 
-import {expect} from '@playwright/test';
+import {expect, type Locator} from '@playwright/test';
 import {test, login_user, load_logged_in_context} from './utils_e2e.ts';
 
 test.beforeAll(async ({browser}, workerInfo) => {
   await login_user(browser, workerInfo, 'user2');
 });
 
-const assertReactionCounts = (comment, counts) =>
+const assertReactionCounts = (comment: Locator, counts: unknown) =>
   expect(async () => {
     await expect(comment.locator('.reactions')).toBeVisible();
 
@@ -29,7 +29,7 @@ const assertReactionCounts = (comment, counts) =>
     return expect(reactions).toStrictEqual(counts);
   }).toPass();
 
-async function toggleReaction(menu, reaction) {
+async function toggleReaction(menu: Locator, reaction: string) {
   await menu.evaluateAll((menus) => menus[0].focus());
   await menu.locator('.add-reaction').click();
   await menu.locator(`[role=menuitem][data-reaction-content="${reaction}"]`).click();
diff --git a/tests/e2e/repo-code.test.e2e.ts b/tests/e2e/repo-code.test.e2e.ts
index d114a9b9c0..d78fa33fe5 100644
--- a/tests/e2e/repo-code.test.e2e.ts
+++ b/tests/e2e/repo-code.test.e2e.ts
@@ -4,7 +4,7 @@
 // services/gitdiff/**
 // @watch end
 
-import {expect} from '@playwright/test';
+import {expect, type Page} from '@playwright/test';
 import {test, login_user, login} from './utils_e2e.ts';
 import {accessibilityCheck} from './shared/accessibility.ts';
 
@@ -12,7 +12,7 @@ test.beforeAll(async ({browser}, workerInfo) => {
   await login_user(browser, workerInfo, 'user2');
 });
 
-async function assertSelectedLines(page, nums) {
+async function assertSelectedLines(page: Page, nums: string[]) {
   const pageAssertions = async () => {
     expect(
       await Promise.all((await page.locator('tr.active [data-line-number]').all()).map((line) => line.getAttribute('data-line-number'))),
diff --git a/tests/e2e/shared/accessibility.ts b/tests/e2e/shared/accessibility.ts
index 6675e0d9eb..1b59d89485 100644
--- a/tests/e2e/shared/accessibility.ts
+++ b/tests/e2e/shared/accessibility.ts
@@ -3,9 +3,9 @@ import {AxeBuilder} from '@axe-core/playwright';
 
 export async function accessibilityCheck({page}: {page: Page}, includes: string[], excludes: string[], disabledRules: string[]) {
   // contrast of inline links is still a global issue in Forgejo
-  disabledRules += 'link-in-text-block';
+  disabledRules.push('link-in-text-block');
 
-  let accessibilityScanner = await new AxeBuilder({page})
+  let accessibilityScanner = new AxeBuilder({page})
     .disableRules(disabledRules);
   // passing the whole array seems to be not supported,
   // iterating has the nice side-effectof skipping this if the array is empty
diff --git a/tests/e2e/utils_e2e.ts b/tests/e2e/utils_e2e.ts
index 89dacce8a4..a52495bcc6 100644
--- a/tests/e2e/utils_e2e.ts
+++ b/tests/e2e/utils_e2e.ts
@@ -33,8 +33,8 @@ export async function login_user(browser: Browser, workerInfo: TestInfo, user: s
   expect(response?.status()).toBe(200); // Status OK
 
   // Fill out form
-  await page.type('input[name=user_name]', user);
-  await page.type('input[name=password]', LOGIN_PASSWORD);
+  await page.fill('input[name=user_name]', user);
+  await page.fill('input[name=password]', LOGIN_PASSWORD);
   await page.click('form button.ui.primary.button:visible');
 
   await page.waitForLoadState();
@@ -48,15 +48,13 @@ export async function login_user(browser: Browser, workerInfo: TestInfo, user: s
 }
 
 export async function load_logged_in_context(browser: Browser, workerInfo: TestInfo, user: string) {
-  let context;
   try {
-    context = await test_context(browser, {storageState: `${ARTIFACTS_PATH}/state-${user}-${workerInfo.workerIndex}.json`});
+    return await test_context(browser, {storageState: `${ARTIFACTS_PATH}/state-${user}-${workerInfo.workerIndex}.json`});
   } catch (err) {
     if (err.code === 'ENOENT') {
       throw new Error(`Could not find state for '${user}'. Did you call login_user(browser, workerInfo, '${user}') in test.beforeAll()?`);
     }
   }
-  return context;
 }
 
 export async function login({browser}: {browser: Browser}, workerInfo: TestInfo) {
diff --git a/tests/e2e/webauthn.test.e2e.ts b/tests/e2e/webauthn.test.e2e.ts
index c351b6a468..98a2d1c152 100644
--- a/tests/e2e/webauthn.test.e2e.ts
+++ b/tests/e2e/webauthn.test.e2e.ts
@@ -30,7 +30,6 @@ test('WebAuthn register & login flow', async ({browser, request}, workerInfo) =>
       transport: 'usb',
       automaticPresenceSimulation: true,
       isUserVerified: true,
-      backupEligibility: true, // TODO: this doesn't seem to be available?!
     },
   });