mirror of
1
Fork 0

[CHORE] Add playwright eslint plugin

- Add https://github.com/playwright-community/eslint-plugin-playwright
as a linter for the playwright tests.
- `no-networkidle` and `no-conditional-in-test` are disabled as fixing
those doesn't seem to really improve testing quality for our use case.
- Some non-recommended linters are enabled to ensure consistency (the
prefer rules).
This commit is contained in:
Gusted 2024-07-22 20:03:32 +02:00
parent d405143919
commit 40baa96fc3
No known key found for this signature in database
GPG Key ID: FD821B732837125F
11 changed files with 66 additions and 20 deletions

26
package-lock.json generated
View File

@ -75,6 +75,7 @@
"eslint-plugin-jquery": "1.5.1", "eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0", "eslint-plugin-no-jquery": "2.7.0",
"eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-playwright": "1.6.2",
"eslint-plugin-regexp": "2.6.0", "eslint-plugin-regexp": "2.6.0",
"eslint-plugin-sonarjs": "0.25.1", "eslint-plugin-sonarjs": "0.25.1",
"eslint-plugin-unicorn": "52.0.0", "eslint-plugin-unicorn": "52.0.0",
@ -6331,6 +6332,31 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/eslint-plugin-playwright": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-playwright/-/eslint-plugin-playwright-1.6.2.tgz",
"integrity": "sha512-mraN4Em3b5jLt01q7qWPyLg0Q5v3KAWfJSlEWwldyUXoa7DSPrBR4k6B6LROLqipsG8ndkwWMdjl1Ffdh15tag==",
"dev": true,
"license": "MIT",
"workspaces": [
"examples"
],
"dependencies": {
"globals": "^13.23.0"
},
"engines": {
"node": ">=16.6.0"
},
"peerDependencies": {
"eslint": ">=8.40.0",
"eslint-plugin-jest": ">=25"
},
"peerDependenciesMeta": {
"eslint-plugin-jest": {
"optional": true
}
}
},
"node_modules/eslint-plugin-prettier": { "node_modules/eslint-plugin-prettier": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz",

View File

@ -74,6 +74,7 @@
"eslint-plugin-jquery": "1.5.1", "eslint-plugin-jquery": "1.5.1",
"eslint-plugin-no-jquery": "2.7.0", "eslint-plugin-no-jquery": "2.7.0",
"eslint-plugin-no-use-extend-native": "0.5.0", "eslint-plugin-no-use-extend-native": "0.5.0",
"eslint-plugin-playwright": "1.6.2",
"eslint-plugin-regexp": "2.6.0", "eslint-plugin-regexp": "2.6.0",
"eslint-plugin-sonarjs": "0.25.1", "eslint-plugin-sonarjs": "0.25.1",
"eslint-plugin-unicorn": "52.0.0", "eslint-plugin-unicorn": "52.0.0",

23
tests/e2e/.eslintrc.yaml Normal file
View File

@ -0,0 +1,23 @@
plugins:
- eslint-plugin-playwright
extends:
- ../../.eslintrc.yaml
- plugin:playwright/recommended
parserOptions:
sourceType: module
ecmaVersion: latest
env:
browser: true
rules:
playwright/no-conditional-in-test: [0]
playwright/no-networkidle: [0]
playwright/no-skipped-test: [2, {allowConditional: true}]
playwright/prefer-comparison-matcher: [2]
playwright/prefer-equality-matcher: [2]
playwright/prefer-to-contain: [2]
playwright/prefer-to-have-length: [2]
playwright/require-to-throw-message: [2]

View File

@ -8,7 +8,7 @@ test.beforeAll(async ({browser}, workerInfo) => {
const workflow_trigger_notification_text = 'This workflow has a workflow_dispatch event trigger.'; const workflow_trigger_notification_text = 'This workflow has a workflow_dispatch event trigger.';
test('Test workflow dispatch present', async ({browser}, workerInfo) => { test('workflow dispatch present', async ({browser}, workerInfo) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2'); const context = await load_logged_in_context(browser, workerInfo, 'user2');
/** @type {import('@playwright/test').Page} */ /** @type {import('@playwright/test').Page} */
const page = await context.newPage(); const page = await context.newPage();
@ -26,7 +26,7 @@ test('Test workflow dispatch present', async ({browser}, workerInfo) => {
await expect(menu).toBeVisible(); await expect(menu).toBeVisible();
}); });
test('Test workflow dispatch error: missing inputs', async ({browser}, workerInfo) => { 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'); 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'); const context = await load_logged_in_context(browser, workerInfo, 'user2');
@ -38,11 +38,8 @@ test('Test workflow dispatch error: missing inputs', async ({browser}, workerInf
await page.locator('#workflow_dispatch_dropdown>button').click(); await page.locator('#workflow_dispatch_dropdown>button').click();
await page.waitForTimeout(1000);
// Remove the required attribute so we can trigger the error message! // Remove the required attribute so we can trigger the error message!
await page.evaluate(() => { await page.evaluate(() => {
// eslint-disable-next-line no-undef
const elem = document.querySelector('input[name="inputs[string2]"]'); const elem = document.querySelector('input[name="inputs[string2]"]');
elem?.removeAttribute('required'); elem?.removeAttribute('required');
}); });
@ -53,7 +50,7 @@ test('Test workflow dispatch error: missing inputs', async ({browser}, workerInf
await expect(page.getByText('Require value for input "String w/o. default".')).toBeVisible(); await expect(page.getByText('Require value for input "String w/o. default".')).toBeVisible();
}); });
test('Test workflow dispatch success', async ({browser}, workerInfo) => { 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'); 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'); const context = await load_logged_in_context(browser, workerInfo, 'user2');
@ -64,7 +61,6 @@ test('Test workflow dispatch success', async ({browser}, workerInfo) => {
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
await page.locator('#workflow_dispatch_dropdown>button').click(); await page.locator('#workflow_dispatch_dropdown>button').click();
await page.waitForTimeout(1000);
await page.type('input[name="inputs[string2]"]', 'abc'); await page.type('input[name="inputs[string2]"]', 'abc');
await page.locator('#workflow-dispatch-submit').click(); await page.locator('#workflow-dispatch-submit').click();
@ -75,7 +71,7 @@ test('Test workflow dispatch success', async ({browser}, workerInfo) => {
await expect(page.locator('.run-list>:first-child .run-list-meta', {hasText: 'now'})).toBeVisible(); await expect(page.locator('.run-list>:first-child .run-list-meta', {hasText: 'now'})).toBeVisible();
}); });
test('Test workflow dispatch box not available for unauthenticated users', async ({page}) => { test('workflow dispatch box not available for unauthenticated users', async ({page}) => {
await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0'); await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0');
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');

View File

@ -19,7 +19,7 @@ test('Switch branch', async ({browser}, workerInfo) => {
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
await expect(page.locator('#loading-indicator')).not.toBeVisible(); await expect(page.locator('#loading-indicator')).toBeHidden();
await expect(page.locator('#rel-container')).toBeVisible(); await expect(page.locator('#rel-container')).toBeVisible();
await expect(page.locator('#rev-container')).toBeVisible(); await expect(page.locator('#rev-container')).toBeVisible();
}); });

View File

@ -13,7 +13,7 @@ test('Load Homepage', async ({page}) => {
await expect(page.locator('.logo')).toHaveAttribute('src', '/assets/img/logo.svg'); await expect(page.locator('.logo')).toHaveAttribute('src', '/assets/img/logo.svg');
}); });
test('Test Register Form', async ({page}, workerInfo) => { test('Register Form', async ({page}, workerInfo) => {
const response = await page.goto('/user/sign_up'); const response = await page.goto('/user/sign_up');
await expect(response?.status()).toBe(200); // Status OK await expect(response?.status()).toBe(200); // Status OK
await page.type('input[name=user_name]', `e2e-test-${workerInfo.workerIndex}`); await page.type('input[name=user_name]', `e2e-test-${workerInfo.workerIndex}`);
@ -29,7 +29,7 @@ test('Test Register Form', async ({page}, workerInfo) => {
save_visual(page); save_visual(page);
}); });
test('Test Login Form', async ({page}, workerInfo) => { test('Login Form', async ({page}, workerInfo) => {
const response = await page.goto('/user/login'); const response = await page.goto('/user/login');
await expect(response?.status()).toBe(200); // Status OK await expect(response?.status()).toBe(200); // Status OK
@ -44,7 +44,7 @@ test('Test Login Form', async ({page}, workerInfo) => {
save_visual(page); save_visual(page);
}); });
test('Test Logged In User', async ({browser}, workerInfo) => { test('Logged In User', async ({browser}, workerInfo) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2'); const context = await load_logged_in_context(browser, workerInfo, 'user2');
const page = await context.newPage(); const page = await context.newPage();

View File

@ -1,6 +1,6 @@
// @ts-check // @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 no-undef: 0 */ // eslint playwright/no-conditional-in-test: 0
import {test, expect} from '@playwright/test'; import {test, expect} from '@playwright/test';
test('Explore view taborder', async ({page}) => { test('Explore view taborder', async ({page}) => {

View File

@ -67,7 +67,7 @@ test('Issue: Labels', async ({browser}, workerInfo) => {
await expect(response?.status()).toBe(200); await expect(response?.status()).toBe(200);
// preconditions // preconditions
await expect(labelList.filter({hasText: 'label1'})).toBeVisible(); await expect(labelList.filter({hasText: 'label1'})).toBeVisible();
await expect(labelList.filter({hasText: 'label2'})).not.toBeVisible(); await expect(labelList.filter({hasText: 'label2'})).toBeHidden();
// add label2 // add label2
await page.locator('.select-label').click(); await page.locator('.select-label').click();
// label search could be tested this way: // label search could be tested this way:
@ -81,7 +81,7 @@ test('Issue: Labels', async ({browser}, workerInfo) => {
await page.locator('.select-label .item').filter({hasText: 'label2'}).click(); await page.locator('.select-label .item').filter({hasText: 'label2'}).click();
await page.locator('.select-label').click(); await page.locator('.select-label').click();
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
await expect(labelList.filter({hasText: 'label2'})).not.toBeVisible(); await expect(labelList.filter({hasText: 'label2'})).toBeHidden();
await expect(labelList.filter({hasText: 'label1'})).toBeVisible(); await expect(labelList.filter({hasText: 'label1'})).toBeVisible();
}); });

View File

@ -6,7 +6,7 @@ test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2'); await login_user(browser, workerInfo, 'user2');
}); });
test('Test markdown indentation', async ({browser}, workerInfo) => { test('markdown indentation', async ({browser}, workerInfo) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2'); const context = await load_logged_in_context(browser, workerInfo, 'user2');
const initText = `* first\n* second\n* third\n* last`; const initText = `* first\n* second\n* third\n* last`;
@ -79,7 +79,7 @@ test('Test markdown indentation', async ({browser}, workerInfo) => {
await expect(textarea).toHaveValue(initText); await expect(textarea).toHaveValue(initText);
}); });
test('Test markdown list continuation', async ({browser}, workerInfo) => { test('markdown list continuation', async ({browser}, workerInfo) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2'); const context = await load_logged_in_context(browser, workerInfo, 'user2');
const initText = `* first\n* second\n* third\n* last`; const initText = `* first\n* second\n* third\n* last`;

View File

@ -1,7 +1,7 @@
// @ts-check // @ts-check
import {test, expect} from '@playwright/test'; import {test, expect} from '@playwright/test';
test('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');
await expect(response?.status()).toBe(200); await expect(response?.status()).toBe(200);
await page.waitForLoadState('networkidle'); await page.waitForLoadState('networkidle');
@ -9,5 +9,5 @@ test('Test markup with #xyz-mode-only', async ({page}) => {
const comment = page.locator('.comment-body>.markup', {hasText: 'test markup light/dark-mode-only'}); const comment = page.locator('.comment-body>.markup', {hasText: 'test markup light/dark-mode-only'});
await expect(comment).toBeVisible(); await expect(comment).toBeVisible();
await expect(comment.locator('[src$="#gh-light-mode-only"]')).toBeVisible(); await expect(comment.locator('[src$="#gh-light-mode-only"]')).toBeVisible();
await expect(comment.locator('[src$="#gh-dark-mode-only"]')).not.toBeVisible(); await expect(comment.locator('[src$="#gh-dark-mode-only"]')).toBeHidden();
}); });

View File

@ -27,7 +27,7 @@ test('Follow actions', async ({browser}, workerInfo) => {
await expect(page.locator('#block-user')).toBeVisible(); await expect(page.locator('#block-user')).toBeVisible();
await page.locator('#block-user .ok').click(); await page.locator('#block-user .ok').click();
await expect(page.locator('.block')).toContainText('Unblock'); await expect(page.locator('.block')).toContainText('Unblock');
await expect(page.locator('#block-user')).not.toBeVisible(); await expect(page.locator('#block-user')).toBeHidden();
// Check that following the user yields in a error being shown. // Check that following the user yields in a error being shown.
await followButton.click(); await followButton.click();