From 40baa96fc3ce330aa50e06798c7bdfb9f33c2cf3 Mon Sep 17 00:00:00 2001 From: Gusted Date: Mon, 22 Jul 2024 20:03:32 +0200 Subject: [PATCH] [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). --- package-lock.json | 26 +++++++++++++++++++ package.json | 1 + tests/e2e/.eslintrc.yaml | 23 ++++++++++++++++ tests/e2e/actions.test.e2e.js | 12 +++------ .../commit-graph-branch-selector.test.e2e.js | 2 +- tests/e2e/example.test.e2e.js | 6 ++--- tests/e2e/explore.test.e2e.js | 2 +- tests/e2e/issue-sidebar.test.e2e.js | 4 +-- tests/e2e/markdown-editor.test.e2e.js | 4 +-- tests/e2e/markup.test.e2e.js | 4 +-- tests/e2e/profile_actions.test.e2e.js | 2 +- 11 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 tests/e2e/.eslintrc.yaml diff --git a/package-lock.json b/package-lock.json index 24feaa355d..509fc8cedb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,6 +75,7 @@ "eslint-plugin-jquery": "1.5.1", "eslint-plugin-no-jquery": "2.7.0", "eslint-plugin-no-use-extend-native": "0.5.0", + "eslint-plugin-playwright": "1.6.2", "eslint-plugin-regexp": "2.6.0", "eslint-plugin-sonarjs": "0.25.1", "eslint-plugin-unicorn": "52.0.0", @@ -6331,6 +6332,31 @@ "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": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", diff --git a/package.json b/package.json index 5e498a50b9..0f0f5509aa 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "eslint-plugin-jquery": "1.5.1", "eslint-plugin-no-jquery": "2.7.0", "eslint-plugin-no-use-extend-native": "0.5.0", + "eslint-plugin-playwright": "1.6.2", "eslint-plugin-regexp": "2.6.0", "eslint-plugin-sonarjs": "0.25.1", "eslint-plugin-unicorn": "52.0.0", diff --git a/tests/e2e/.eslintrc.yaml b/tests/e2e/.eslintrc.yaml new file mode 100644 index 0000000000..390b2de5c4 --- /dev/null +++ b/tests/e2e/.eslintrc.yaml @@ -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] diff --git a/tests/e2e/actions.test.e2e.js b/tests/e2e/actions.test.e2e.js index dcbd123e0b..0a4695e4a2 100644 --- a/tests/e2e/actions.test.e2e.js +++ b/tests/e2e/actions.test.e2e.js @@ -8,7 +8,7 @@ test.beforeAll(async ({browser}, workerInfo) => { 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'); /** @type {import('@playwright/test').Page} */ const page = await context.newPage(); @@ -26,7 +26,7 @@ test('Test workflow dispatch present', async ({browser}, workerInfo) => { 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'); 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.waitForTimeout(1000); - // Remove the required attribute so we can trigger the error message! await page.evaluate(() => { - // eslint-disable-next-line no-undef const elem = document.querySelector('input[name="inputs[string2]"]'); 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(); }); -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'); 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.locator('#workflow_dispatch_dropdown>button').click(); - await page.waitForTimeout(1000); await page.type('input[name="inputs[string2]"]', 'abc'); 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(); }); -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.waitForLoadState('networkidle'); diff --git a/tests/e2e/commit-graph-branch-selector.test.e2e.js b/tests/e2e/commit-graph-branch-selector.test.e2e.js index b19277c114..4994c948b4 100644 --- a/tests/e2e/commit-graph-branch-selector.test.e2e.js +++ b/tests/e2e/commit-graph-branch-selector.test.e2e.js @@ -19,7 +19,7 @@ test('Switch branch', async ({browser}, workerInfo) => { 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('#rev-container')).toBeVisible(); }); diff --git a/tests/e2e/example.test.e2e.js b/tests/e2e/example.test.e2e.js index c185d9157b..effb9f31b9 100644 --- a/tests/e2e/example.test.e2e.js +++ b/tests/e2e/example.test.e2e.js @@ -13,7 +13,7 @@ test('Load Homepage', async ({page}) => { 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'); await expect(response?.status()).toBe(200); // Status OK 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); }); -test('Test Login Form', async ({page}, workerInfo) => { +test('Login Form', async ({page}, workerInfo) => { const response = await page.goto('/user/login'); await expect(response?.status()).toBe(200); // Status OK @@ -44,7 +44,7 @@ test('Test Login Form', async ({page}, workerInfo) => { 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 page = await context.newPage(); diff --git a/tests/e2e/explore.test.e2e.js b/tests/e2e/explore.test.e2e.js index f486a3cb5f..1b7986242f 100644 --- a/tests/e2e/explore.test.e2e.js +++ b/tests/e2e/explore.test.e2e.js @@ -1,6 +1,6 @@ // @ts-check // 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'; test('Explore view taborder', async ({page}) => { diff --git a/tests/e2e/issue-sidebar.test.e2e.js b/tests/e2e/issue-sidebar.test.e2e.js index 4bd211abe5..7f343bd3b3 100644 --- a/tests/e2e/issue-sidebar.test.e2e.js +++ b/tests/e2e/issue-sidebar.test.e2e.js @@ -67,7 +67,7 @@ test('Issue: Labels', async ({browser}, workerInfo) => { await expect(response?.status()).toBe(200); // preconditions await expect(labelList.filter({hasText: 'label1'})).toBeVisible(); - await expect(labelList.filter({hasText: 'label2'})).not.toBeVisible(); + await expect(labelList.filter({hasText: 'label2'})).toBeHidden(); // add label2 await page.locator('.select-label').click(); // 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').click(); 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(); }); diff --git a/tests/e2e/markdown-editor.test.e2e.js b/tests/e2e/markdown-editor.test.e2e.js index 144519875a..e773c70845 100644 --- a/tests/e2e/markdown-editor.test.e2e.js +++ b/tests/e2e/markdown-editor.test.e2e.js @@ -6,7 +6,7 @@ test.beforeAll(async ({browser}, workerInfo) => { 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 initText = `* first\n* second\n* third\n* last`; @@ -79,7 +79,7 @@ test('Test markdown indentation', async ({browser}, workerInfo) => { 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 initText = `* first\n* second\n* third\n* last`; diff --git a/tests/e2e/markup.test.e2e.js b/tests/e2e/markup.test.e2e.js index 7bc6d2b6ca..ff4e948d8f 100644 --- a/tests/e2e/markup.test.e2e.js +++ b/tests/e2e/markup.test.e2e.js @@ -1,7 +1,7 @@ // @ts-check 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'); await expect(response?.status()).toBe(200); 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'}); await expect(comment).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(); }); diff --git a/tests/e2e/profile_actions.test.e2e.js b/tests/e2e/profile_actions.test.e2e.js index 20155b8df4..aeccab019c 100644 --- a/tests/e2e/profile_actions.test.e2e.js +++ b/tests/e2e/profile_actions.test.e2e.js @@ -27,7 +27,7 @@ test('Follow actions', async ({browser}, workerInfo) => { await expect(page.locator('#block-user')).toBeVisible(); await page.locator('#block-user .ok').click(); 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. await followButton.click();