e2e: Selective screenshots (#9499)
* Add some test that only snapshot relevant content * Allow adding marging around the element in case the environment is relevant (e.g. the location of an element relative to the parent, but excluding the environment of the parent) Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9499 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Co-authored-by: Otto Richter <git@otto.splvs.net> Co-committed-by: Otto Richter <git@otto.splvs.net>
This commit is contained in:
parent
cb7d2057ce
commit
f87b76160e
34 changed files with 264 additions and 186 deletions
|
@ -10,7 +10,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect, type Page, type TestInfo} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
const workflow_trigger_notification_text = 'This workflow has a workflow_dispatch event trigger.';
|
||||
|
||||
|
@ -21,13 +22,13 @@ async function dispatchSuccess(page: Page, testInfo: TestInfo) {
|
|||
await page.locator('#workflow_dispatch_dropdown>button').click();
|
||||
|
||||
await page.fill('input[name="inputs[string2]"]', 'abc');
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('div.ui.container').filter({hasText: 'All workflows'}));
|
||||
await page.locator('#workflow-dispatch-submit').click();
|
||||
|
||||
await expect(page.getByText('Workflow run was successfully requested.')).toBeVisible();
|
||||
|
||||
await expect(page.locator('.run-list>:first-child .run-list-meta', {hasText: 'now'})).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('div.ui.container').filter({hasText: 'All workflows'}));
|
||||
}
|
||||
|
||||
test.describe('Workflow Authenticated user2', () => {
|
||||
|
@ -45,7 +46,7 @@ test.describe('Workflow Authenticated user2', () => {
|
|||
await expect(menu).toBeHidden();
|
||||
await run_workflow_btn.click();
|
||||
await expect(menu).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('div.ui.container').filter({hasText: 'All workflows'}));
|
||||
});
|
||||
|
||||
test('dispatch error: missing inputs', async ({page}, testInfo) => {
|
||||
|
@ -64,7 +65,7 @@ test.describe('Workflow Authenticated user2', () => {
|
|||
await page.locator('#workflow-dispatch-submit').click();
|
||||
|
||||
await expect(page.getByText('Require value for input "String w/o. default".')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('div.ui.container').filter({hasText: 'All workflows'}));
|
||||
});
|
||||
|
||||
// no assertions as the login in this test case is extracted for reuse
|
||||
|
@ -78,7 +79,7 @@ test('workflow dispatch box not available for unauthenticated users', async ({pa
|
|||
await page.goto('/user2/test_workflows/actions?workflow=test-dispatch.yml&actor=0&status=0');
|
||||
|
||||
await expect(page.locator('body')).not.toContainText(workflow_trigger_notification_text);
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('div.ui.container').filter({hasText: 'All workflows'}));
|
||||
});
|
||||
|
||||
async function completeDynamicRefresh(page: Page) {
|
||||
|
@ -119,7 +120,7 @@ test.describe('workflow list dynamic refresh', () => {
|
|||
});
|
||||
await completeDynamicRefresh(page);
|
||||
await expect(backgroundPage.locator('.run-list>:first-child .flex-item-body>b', {hasText: latestDispatchedRun})).toBeVisible();
|
||||
await save_visual(backgroundPage);
|
||||
await screenshot(backgroundPage, page.locator('div.ui.container').filter({hasText: 'All workflows'}));
|
||||
});
|
||||
|
||||
test('refreshes on interval', async ({page}, testInfo) => {
|
||||
|
@ -137,7 +138,7 @@ test.describe('workflow list dynamic refresh', () => {
|
|||
|
||||
await simulatePollingInterval(backgroundPage);
|
||||
await expect(backgroundPage.locator('.run-list>:first-child .flex-item-body>b', {hasText: latestDispatchedRun})).toBeVisible();
|
||||
await save_visual(backgroundPage);
|
||||
await screenshot(backgroundPage, page.locator('div.ui.container').filter({hasText: 'All workflows'}));
|
||||
});
|
||||
|
||||
test('post-refresh the dropdowns continue to operate', async ({page}, testInfo) => {
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test('copy src file path to clipboard', async ({page}, workerInfo) => {
|
||||
test.skip(['Mobile Safari', 'webkit'].includes(workerInfo.project.name), 'Apple clipboard API addon - starting at just $499!');
|
||||
|
@ -20,7 +21,7 @@ test('copy src file path to clipboard', async ({page}, workerInfo) => {
|
|||
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
|
||||
expect(clipboardText).toContain('README.md');
|
||||
await expect(page.getByText('Copied')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.getByText('Copied'), 50);
|
||||
});
|
||||
|
||||
test('copy diff file path to clipboard', async ({page}, workerInfo) => {
|
||||
|
@ -33,5 +34,5 @@ test('copy diff file path to clipboard', async ({page}, workerInfo) => {
|
|||
const clipboardText = await page.evaluate(() => navigator.clipboard.readText());
|
||||
expect(clipboardText).toContain('README.md');
|
||||
await expect(page.getByText('Copied')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.getByText('Copied'), 50);
|
||||
});
|
||||
|
|
|
@ -24,5 +24,5 @@ test('Correct link and tooltip', async ({page}, testInfo) => {
|
|||
await expect(repoStatus).toHaveAttribute('href', '/user2/test_workflows/actions', {timeout: 10000});
|
||||
await expect(repoStatus).toHaveAttribute('data-tooltip-content', /^(Error|Failure)$/);
|
||||
// ToDo: Ensure stable screenshot of dashboard. Known to be flaky: https://code.forgejo.org/forgejo/visual-browser-testing/commit/206d4cfb7a4af6d8d7043026cdd4d63708798b2a
|
||||
// await save_visual(page);
|
||||
// await screenshot(page);
|
||||
});
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -23,13 +24,13 @@ test('Dimmed modal', async ({page}) => {
|
|||
// Modal and dimmer should be visible.
|
||||
await expect(page.locator('#block-user')).toBeVisible();
|
||||
await expect(page.locator('.ui.dimmer')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.ui.g-modal-confirm.delete.modal'), 50);
|
||||
|
||||
// After canceling, modal and dimmer should be hidden.
|
||||
await page.locator('#block-user .cancel').click();
|
||||
await expect(page.locator('.ui.dimmer')).toBeHidden();
|
||||
await expect(page.locator('#block-user')).toBeHidden();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
// Open the block modal and make the dimmer visible again.
|
||||
await page.locator('.actions .dropdown').click();
|
||||
|
@ -37,7 +38,7 @@ test('Dimmed modal', async ({page}) => {
|
|||
await expect(page.locator('#block-user')).toBeVisible();
|
||||
await expect(page.locator('.ui.dimmer')).toBeVisible();
|
||||
await expect(page.locator('.ui.dimmer')).toHaveCount(1);
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.ui.g-modal-confirm.delete.modal'), 50);
|
||||
});
|
||||
|
||||
test('Dimmed overflow', async ({page}, workerInfo) => {
|
||||
|
@ -58,7 +59,7 @@ test('Dimmed overflow', async ({page}, workerInfo) => {
|
|||
// Expect a 'are you sure, this file is empty' modal.
|
||||
await expect(page.locator('#edit-empty-content-modal')).toBeVisible();
|
||||
await expect(page.locator('#edit-empty-content-modal header')).toContainText('Commit an empty file');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
// Trickery to check the page cannot be scrolled.
|
||||
const {overflow} = await page.evaluate(() => {
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test('Load Homepage', async ({page}) => {
|
||||
const response = await page.goto('/');
|
||||
|
@ -26,7 +27,7 @@ test('Register Form', async ({page}, workerInfo) => {
|
|||
expect(page.url()).toBe(`${workerInfo.project.use.baseURL}/`);
|
||||
await expect(page.locator('.secondary-nav span>img.ui.avatar')).toBeVisible();
|
||||
await expect(page.locator('.ui.positive.message.flash-success')).toHaveText('Account was successfully created. Welcome!');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line playwright/no-skipped-test
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test('Explore view taborder', async ({page}) => {
|
||||
await page.goto('/explore/repos');
|
||||
|
@ -42,5 +43,5 @@ test('Explore view taborder', async ({page}) => {
|
|||
}
|
||||
}
|
||||
expect(res).toBe(exp);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @ts-check
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -16,15 +17,15 @@ test('Change git note', async ({page}) => {
|
|||
let textarea = page.locator('textarea[name="notes"]');
|
||||
await expect(textarea).toBeVisible();
|
||||
await textarea.fill('This is a new note');
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.ui.container.fluid.padded'));
|
||||
|
||||
await page.locator('#notes-save-button').click();
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.ui.container.fluid.padded'));
|
||||
|
||||
response = await page.goto('/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d');
|
||||
expect(response?.status()).toBe(200);
|
||||
|
||||
textarea = page.locator('textarea[name="notes"]');
|
||||
await expect(textarea).toHaveText('This is a new note');
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.ui.container.fluid.padded'));
|
||||
});
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test, dynamic_id} from './utils_e2e.ts';
|
||||
import {test, dynamic_id} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -44,7 +45,7 @@ test('Repository image diff', async ({page}) => {
|
|||
await expect(page.locator('.tab[data-tab="diff-side-by-side-1"]')).toBeVisible();
|
||||
await expect(page.locator('.tab[data-tab="diff-swipe-1"]')).toBeHidden();
|
||||
await expect(page.locator('.tab[data-tab="diff-overlay-1"]')).toBeHidden();
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('#diff-container'));
|
||||
|
||||
await page.getByText('Swipe').click();
|
||||
await expect(page.locator('.item[data-tab="diff-side-by-side-1"]')).not.toContainClass('active');
|
||||
|
@ -53,7 +54,7 @@ test('Repository image diff', async ({page}) => {
|
|||
await expect(page.locator('.tab[data-tab="diff-side-by-side-1"]')).toBeHidden();
|
||||
await expect(page.locator('.tab[data-tab="diff-swipe-1"]')).toBeVisible();
|
||||
await expect(page.locator('.tab[data-tab="diff-overlay-1"]')).toBeHidden();
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('#diff-container'));
|
||||
|
||||
await page.getByText('Overlay').click();
|
||||
await expect(page.locator('.item[data-tab="diff-side-by-side-1"]')).not.toContainClass('active');
|
||||
|
@ -62,5 +63,5 @@ test('Repository image diff', async ({page}) => {
|
|||
await expect(page.locator('.tab[data-tab="diff-side-by-side-1"]')).toBeHidden();
|
||||
await expect(page.locator('.tab[data-tab="diff-swipe-1"]')).toBeHidden();
|
||||
await expect(page.locator('.tab[data-tab="diff-overlay-1"]')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('#diff-container'));
|
||||
});
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect, type Locator, type Page, type TestInfo} from '@playwright/test';
|
||||
import {test, save_visual, dynamic_id} from './utils_e2e.ts';
|
||||
import {test, dynamic_id} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -67,7 +68,7 @@ test('Paste image in new comment', async ({page}, workerInfo) => {
|
|||
await expect(preview.locator('.octicon-copy')).toBeVisible();
|
||||
await assertCopy(page, workerInfo, ';
|
||||
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.issue-content-left'));
|
||||
});
|
||||
|
||||
test('Re-add images to dropzone on edit', async ({page}, workerInfo) => {
|
||||
|
@ -90,5 +91,5 @@ test('Re-add images to dropzone on edit', async ({page}, workerInfo) => {
|
|||
await expect(preview.locator('.octicon-copy')).toBeVisible();
|
||||
await assertCopy(page, workerInfo, ';
|
||||
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.issue-content-left'));
|
||||
});
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {test, dynamic_id, save_visual} from './utils_e2e.ts';
|
||||
import {test, dynamic_id} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -75,7 +76,7 @@ test('Always focus edit tab first on edit', async ({page}) => {
|
|||
|
||||
await expect(editTab).toHaveClass(/active/);
|
||||
await expect(previewTab).not.toHaveClass(/active/);
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.issue-content-left'));
|
||||
});
|
||||
|
||||
test('Reset content of comment edit field on cancel', async ({page}) => {
|
||||
|
@ -96,7 +97,7 @@ test('Reset content of comment edit field on cancel', async ({page}) => {
|
|||
await page.click('#issue-1 .comment-container .context-menu');
|
||||
await page.click('#issue-1 .comment-container .menu>.edit-content');
|
||||
await expect(editorTextarea).toHaveValue('content for the first issue');
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.issue-content-left'));
|
||||
});
|
||||
|
||||
test('Quote reply', async ({page}, workerInfo) => {
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
/* eslint playwright/expect-expect: ["error", { "assertFunctionNames": ["check_wip"] }] */
|
||||
|
||||
import {expect, type Page} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -194,7 +195,7 @@ test('New Issue: Assignees', async ({page}, workerInfo) => {
|
|||
await page.locator('.select-assignees .menu .item').filter({hasText: 'user4'}).click();
|
||||
await page.locator('.select-assignees.dropdown').click();
|
||||
await expect(assigneesList.filter({hasText: 'user4'})).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.issue-content-right'));
|
||||
|
||||
// remove user4
|
||||
await page.locator('.select-assignees.dropdown').click();
|
||||
|
@ -212,7 +213,7 @@ test('New Issue: Assignees', async ({page}, workerInfo) => {
|
|||
await page.fill('.select-assignees .menu .search input', '');
|
||||
await page.locator('.select-assignees.dropdown .no-select.item').click();
|
||||
await expect(page.locator('.select-assign-me')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('div.filter.menu[data-id="#assignee_ids"]'), 30);
|
||||
});
|
||||
|
||||
test('Issue: Milestone', async ({page}, workerInfo) => {
|
||||
|
@ -247,19 +248,20 @@ test('New Issue: Milestone', async ({page}, workerInfo) => {
|
|||
const selectedMilestone = page.locator('.issue-content-right .select-milestone.list');
|
||||
const milestoneDropdown = page.locator('.issue-content-right .select-milestone.dropdown');
|
||||
await expect(selectedMilestone).toContainText('No milestone');
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.issue-content-right'));
|
||||
|
||||
// Add milestone.
|
||||
await milestoneDropdown.click();
|
||||
await screenshot(page, page.locator('.menu.transition.visible'), 30);
|
||||
await page.getByRole('option', {name: 'milestone1'}).click();
|
||||
await expect(selectedMilestone).toContainText('milestone1');
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.issue-content-right'));
|
||||
|
||||
// Clear milestone.
|
||||
await milestoneDropdown.click();
|
||||
await page.getByText('Clear milestone', {exact: true}).click();
|
||||
await expect(selectedMilestone).toContainText('No milestone');
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.issue-content-right'));
|
||||
});
|
||||
|
||||
test.describe('Dependency dropdown', () => {
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {test, save_visual} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -31,7 +32,7 @@ test('Issue timetracking', async ({page}) => {
|
|||
// Verify it is shown in the issue sidebar
|
||||
await expect(page.locator('.issue-content-right .comments')).toContainText('Total time spent: 5 hours 32 minutes');
|
||||
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
// Delete the added time.
|
||||
await page.getByRole('button', {name: 'Delete this time log'}).click();
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {test, save_visual, test_context} from './utils_e2e.ts';
|
||||
import {test, test_context} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test('Mismatched ROOT_URL', async ({browser}, workerInfo) => {
|
||||
test.skip(['Mobile Safari', 'webkit'].includes(workerInfo.project.name), 'init script gets randomly ignored');
|
||||
|
@ -27,7 +28,7 @@ test('Mismatched ROOT_URL', async ({browser}, workerInfo) => {
|
|||
const response = await page.goto('/user/login');
|
||||
expect(response?.status()).toBe(200);
|
||||
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
const globalError = page.locator('.js-global-error');
|
||||
await expect(globalError).toContainText('This Forgejo instance is configured to be served on ');
|
||||
await expect(globalError).toContainText('You are currently viewing Forgejo through a different URL, which may cause parts of the application to break. The canonical URL is controlled by Forgejo admins via the ROOT_URL setting in the app.ini.');
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
|
||||
import {expect} from '@playwright/test';
|
||||
import {accessibilityCheck} from './shared/accessibility.ts';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -38,7 +39,7 @@ test('Markdown image preview behaviour', async ({page}, workerInfo) => {
|
|||
// Check for the image preview via the expected attribute
|
||||
const preview = page.locator('div[data-tab="preview"] p[dir="auto"] a');
|
||||
await expect(preview).toHaveAttribute('href', 'http://localhost:3003/user2/repo1/media/branch/master/assets/logo.svg');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('Markdown indentation via toolbar', async ({page}) => {
|
||||
|
@ -359,7 +360,7 @@ test('Markdown insert table', async ({page}) => {
|
|||
|
||||
const newTableModal = page.locator('div[data-markdown-table-modal-id="0"]');
|
||||
await expect(newTableModal).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
await newTableModal.locator('input[name="table-rows"]').fill('3');
|
||||
await newTableModal.locator('input[name="table-columns"]').fill('2');
|
||||
|
@ -370,7 +371,7 @@ test('Markdown insert table', async ({page}) => {
|
|||
|
||||
const textarea = page.locator('textarea[name=content]');
|
||||
await expect(textarea).toHaveValue('| Header | Header |\n|---------|---------|\n| Content | Content |\n| Content | Content |\n| Content | Content |\n');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('Markdown insert link', async ({page}) => {
|
||||
|
@ -383,7 +384,7 @@ test('Markdown insert link', async ({page}) => {
|
|||
const newLinkModal = page.locator('div[data-markdown-link-modal-id="0"]');
|
||||
await expect(newLinkModal).toBeVisible();
|
||||
await accessibilityCheck({page}, ['[data-modal-name="new-markdown-link"]'], [], []);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
const url = 'https://example.com';
|
||||
const description = 'Where does this lead?';
|
||||
|
@ -397,7 +398,7 @@ test('Markdown insert link', async ({page}) => {
|
|||
|
||||
const textarea = page.locator('textarea[name=content]');
|
||||
await expect(textarea).toHaveValue(`[${description}](${url})`);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('text expander has higher prio then prefix continuation', async ({page}) => {
|
||||
|
@ -451,7 +452,7 @@ test('Combo Markdown: preview mode switch', async ({page}) => {
|
|||
await expect(toolbarItem).toBeHidden();
|
||||
await expect(editorPanel).toBeHidden();
|
||||
await expect(previewPanel).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
// Verify that some content rendered
|
||||
await expect(page.locator('[data-tab-panel="markdown-previewer"] .emoji[data-alias="100"]')).toBeVisible();
|
||||
|
@ -463,7 +464,7 @@ test('Combo Markdown: preview mode switch', async ({page}) => {
|
|||
await expect(toolbarItem).toBeVisible();
|
||||
await expect(editorPanel).toBeVisible();
|
||||
await expect(previewPanel).toBeHidden();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('Multiple combo markdown: insert table', async ({page}) => {
|
||||
|
@ -500,7 +501,7 @@ test('Multiple combo markdown: insert table', async ({page}) => {
|
|||
|
||||
await expect(comboboxOne).toHaveValue('| Header | Header |\n|---------|---------|\n| Content | Content |\n| Content | Content |\n| Content | Content |\n');
|
||||
await expect(comboboxTwo).toBeEmpty();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
// focus second one and add table to it
|
||||
await textareaTwo.click();
|
||||
|
@ -522,7 +523,7 @@ test('Multiple combo markdown: insert table', async ({page}) => {
|
|||
|
||||
await expect(comboboxOne).toHaveValue('| Header | Header |\n|---------|---------|\n| Content | Content |\n| Content | Content |\n| Content | Content |\n');
|
||||
await expect(comboboxTwo).toHaveValue('| Header | Header | Header |\n|---------|---------|---------|\n| Content | Content | Content |\n| Content | Content | Content |\n');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('Markdown bold/italic toolbar and shortcut', async ({page}) => {
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test('markup with #xyz-mode-only', async ({page}, workerInfo) => {
|
||||
test.skip(['webkit', 'Mobile Safari'].includes(workerInfo.project.name), 'Newest version contains a regression');
|
||||
|
@ -14,5 +15,5 @@ test('markup with #xyz-mode-only', async ({page}, workerInfo) => {
|
|||
await expect(comment).toBeVisible();
|
||||
await expect(comment.locator('[src$="#gh-light-mode-only"]')).toBeVisible();
|
||||
await expect(comment.locator('[src$="#gh-dark-mode-only"]')).toBeHidden();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, dynamic_id, test} from './utils_e2e.ts';
|
||||
import {dynamic_id, test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -33,7 +34,7 @@ test('Dialog modal', async ({page}, workerInfo) => {
|
|||
await page.keyboard.press('Backspace');
|
||||
|
||||
await page.locator('#commit-button').click();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await expect(page.locator('#edit-empty-content-modal')).toBeVisible();
|
||||
|
||||
await page.locator('#edit-empty-content-modal .cancel').click();
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
import {validate_form} from './shared/forms.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
@ -17,11 +18,11 @@ test('org team settings', async ({page}, workerInfo) => {
|
|||
|
||||
await page.locator('input[name="permission"][value="admin"]').click();
|
||||
await expect(page.locator('.hide-unless-checked')).toBeHidden();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
await page.locator('input[name="permission"][value="read"]').click();
|
||||
await expect(page.locator('.hide-unless-checked')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
// we are validating the form here to include the part that could be hidden
|
||||
await validate_form({page});
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -16,18 +17,18 @@ test('PR: Create review from files', async ({page}) => {
|
|||
expect(response?.status()).toBe(200);
|
||||
|
||||
await expect(page.locator('.tippy-box .review-box-panel')).toBeHidden();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
// Review panel should appear after clicking Finish review
|
||||
await page.locator('#review-box .js-btn-review').click();
|
||||
await expect(page.locator('.tippy-box .review-box-panel')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
await page.locator('.review-box-panel textarea#_combo_markdown_editor_0')
|
||||
.fill('This is a review');
|
||||
await page.locator('.review-box-panel button.btn-submit[value="approve"]').click();
|
||||
await page.waitForURL(/.*\/user2\/repo1\/pulls\/5#issuecomment-\d+/);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('PR: Create review from commit', async ({page}) => {
|
||||
|
@ -39,7 +40,7 @@ test('PR: Create review from commit', async ({page}) => {
|
|||
await expect(code_comment).toBeVisible();
|
||||
|
||||
await code_comment.fill('This is a code comment');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
const start_button = page.locator('.comment-code-cloud form button.btn-start-review');
|
||||
// Workaround for #7152, where there might already be a pending review state from previous
|
||||
|
@ -58,13 +59,13 @@ test('PR: Create review from commit', async ({page}) => {
|
|||
|
||||
await page.locator('#review-box .js-btn-review').click();
|
||||
await expect(page.locator('.tippy-box .review-box-panel')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
await page.locator('.review-box-panel textarea.markdown-text-editor')
|
||||
.fill('This is a review');
|
||||
await page.locator('.review-box-panel button.btn-submit[value="approve"]').click();
|
||||
await page.waitForURL(/.*\/user2\/repo1\/pulls\/3#issuecomment-\d+/);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
// In addition to testing the ability to delete comments, this also
|
||||
// performs clean up. If tests are run for multiple platforms, the data isn't reset
|
||||
|
@ -79,7 +80,7 @@ test('PR: Create review from commit', async ({page}) => {
|
|||
await page.locator('.comment-header-right.actions div.menu .delete-comment').click();
|
||||
|
||||
await expect(page.locator('.comment-list .comment-container')).toBeHidden();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('PR: Navigate by single commit', async ({page}) => {
|
||||
|
@ -88,7 +89,7 @@ test('PR: Navigate by single commit', async ({page}) => {
|
|||
|
||||
await page.locator('tbody.commit-list td.message a').nth(1).click();
|
||||
await page.waitForURL(/.*\/user2\/repo1\/pulls\/3\/commits\/4a357436d925b5c974181ff12a994538ddc5a269/);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
let prevButton = page.locator('.commit-header-buttons').getByText(/Prev/);
|
||||
let nextButton = page.locator('.commit-header-buttons').getByText(/Next/);
|
||||
|
@ -101,7 +102,7 @@ test('PR: Navigate by single commit', async ({page}) => {
|
|||
await nextButton.click();
|
||||
|
||||
await page.waitForURL(/.*\/user2\/repo1\/pulls\/3\/commits\/5f22f7d0d95d614d25a5b68592adb345a4b5c7fd/);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
prevButton = page.locator('.commit-header-buttons').getByText(/Prev/);
|
||||
nextButton = page.locator('.commit-header-buttons').getByText(/Next/);
|
||||
|
@ -122,7 +123,7 @@ test('PR: Test mentions values', async ({page}) => {
|
|||
|
||||
await page.locator('.review-box-panel textarea#_combo_markdown_editor_0')
|
||||
.fill('@');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
await expect(page.locator('ul.suggestions li span:first-of-type')).toContainText([
|
||||
'user1',
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -16,10 +17,10 @@ test('PR: title edit', async ({page}) => {
|
|||
expect(response?.status()).toBe(200);
|
||||
|
||||
await expect(page.locator('#editable-label')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
// Labels AGit and Editable are hidden when title is in edit mode
|
||||
await page.locator('#issue-title-edit-show').click();
|
||||
await expect(page.locator('#editable-label')).toBeHidden();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -32,7 +33,7 @@ test('Follow and block actions', async ({page}) => {
|
|||
|
||||
await blockButton.click();
|
||||
await expect(page.locator('#block-user')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await page.locator('#block-user .ok').click();
|
||||
await expect(blockButton).toContainText('Unblock');
|
||||
await expect(page.locator('#block-user')).toBeHidden();
|
||||
|
@ -42,7 +43,7 @@ test('Follow and block actions', async ({page}) => {
|
|||
const flashMessage = page.locator('#flash-message');
|
||||
await expect(flashMessage).toBeVisible();
|
||||
await expect(flashMessage).toContainText('You cannot follow this user because you have blocked this user or this user has blocked you.');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
// Unblock interaction.
|
||||
await actionsDropdownBtn.click();
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect, type Locator} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -62,5 +63,5 @@ test('Reaction Selectors', async ({page}) => {
|
|||
|
||||
await toggleReaction(topPicker, 'laugh');
|
||||
await assertReactionCounts(comment, {'laugh': 2});
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
|
|
@ -9,12 +9,13 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
import {validate_form} from './shared/forms.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
test.describe('repo branch protection settings', () => {
|
||||
test.describe('Releases', () => {
|
||||
test('External Release Attachments', async ({page, isMobile}, workerInfo) => {
|
||||
test.skip(isMobile || workerInfo.project.name === 'webkit');
|
||||
|
||||
|
@ -33,7 +34,7 @@ test.describe('repo branch protection settings', () => {
|
|||
await page.fill('input[name=attachment-new-name-2]', 'Test');
|
||||
await page.fill('input[name=attachment-new-exturl-2]', 'https://forgejo.org/');
|
||||
await page.click('.remove-rel-attach');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await page.click('.button.small.primary');
|
||||
|
||||
// Validate release page and click edit
|
||||
|
@ -52,7 +53,7 @@ test.describe('repo branch protection settings', () => {
|
|||
|
||||
await expect(page.locator('.download[open] li:nth-of-type(3)')).toContainText('Test');
|
||||
await expect(page.locator('.download[open] li:nth-of-type(3) a')).toHaveAttribute('href', 'https://forgejo.org/');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await page.locator('.octicon-pencil').first().click();
|
||||
|
||||
// Validate edit page and edit the release
|
||||
|
@ -67,7 +68,7 @@ test.describe('repo branch protection settings', () => {
|
|||
await expect(page.locator('.attachment_edit:visible')).toHaveCount(4);
|
||||
await page.locator('.attachment_edit:visible').nth(2).fill('Test3');
|
||||
await page.locator('.attachment_edit:visible').nth(3).fill('https://gitea.com/');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await page.click('.button.small.primary');
|
||||
|
||||
// Validate release page and click edit
|
||||
|
@ -77,7 +78,7 @@ test.describe('repo branch protection settings', () => {
|
|||
await expect(page.locator('.download[open] li:nth-of-type(3) a')).toHaveAttribute('href', 'https://gitea.io/');
|
||||
await expect(page.locator('.download[open] li:nth-of-type(4)')).toContainText('Test3');
|
||||
await expect(page.locator('.download[open] li:nth-of-type(4) a')).toHaveAttribute('href', 'https://gitea.com/');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await page.locator('.octicon-pencil').first().click();
|
||||
});
|
||||
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect, type Page} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
import {accessibilityCheck} from './shared/accessibility.ts';
|
||||
|
||||
async function assertSelectedLines(page: Page, nums: string[]) {
|
||||
|
@ -55,7 +56,7 @@ test('Line Range Selection', async ({page}) => {
|
|||
// out-of-bounds end line
|
||||
await page.goto(`${filePath}#L1-L100`);
|
||||
await assertSelectedLines(page, ['1', '2', '3']);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('Readable diff', async ({page}, workerInfo) => {
|
||||
|
@ -82,7 +83,7 @@ test('Readable diff', async ({page}, workerInfo) => {
|
|||
await expect(page.getByText(thisDiff.added, {exact: true})).toHaveCSS('background-color', 'rgb(134, 239, 172)');
|
||||
}
|
||||
}
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test.describe('As authenticated user', () => {
|
||||
|
@ -95,14 +96,14 @@ test.describe('As authenticated user', () => {
|
|||
await expect(page.getByRole('link', {name: '@user2'})).toHaveCSS('background-color', /(.*)/);
|
||||
await expect(page.getByRole('link', {name: '@user1'})).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
|
||||
await accessibilityCheck({page}, ['.commit-header'], [], []);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
// check second commit
|
||||
await page.goto('/user2/mentions-highlighted/commits/branch/main');
|
||||
await page.locator('tbody').getByRole('link', {name: 'Another commit which mentions'}).click();
|
||||
await expect(page.getByRole('link', {name: '@user2'})).toHaveCSS('background-color', /(.*)/);
|
||||
await expect(page.getByRole('link', {name: '@user1'})).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
|
||||
await accessibilityCheck({page}, ['.commit-header'], [], []);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test('Commit graph overflow', async ({page}) => {
|
||||
const response = await page.goto('/user2/repo1/graph');
|
||||
|
@ -28,7 +29,7 @@ test('Commit graph overflow', async ({page}) => {
|
|||
await expect(page.getByRole('button', {name: 'Mono'})).toBeInViewport({ratio: 1});
|
||||
await expect(page.getByRole('button', {name: 'Color'})).toBeInViewport({ratio: 1});
|
||||
await expect(page.locator('.selection.search.dropdown')).toBeInViewport({ratio: 1});
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('Switch branch', async ({page}) => {
|
||||
|
@ -45,5 +46,5 @@ test('Switch branch', async ({page}) => {
|
|||
await expect(page.locator('#loading-indicator')).toBeHidden();
|
||||
await expect(page.locator('#rel-container')).toBeVisible();
|
||||
await expect(page.locator('#rev-container')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test('Language stats bar', async ({page}) => {
|
||||
const response = await page.goto('/user2/language-stats-test');
|
||||
|
@ -15,11 +16,11 @@ test('Language stats bar', async ({page}) => {
|
|||
|
||||
await page.click('#language-stats-bar');
|
||||
await expect(page.locator('#language-stats-legend')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
await page.click('#language-stats-bar');
|
||||
await expect(page.locator('#language-stats-legend')).toBeHidden();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('Branch selector commit icon', async ({page}) => {
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {test, save_visual, test_context, dynamic_id} from './utils_e2e.ts';
|
||||
import {test, test_context, dynamic_id} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
||||
|
@ -22,7 +23,7 @@ test('Migration type seleciton screen', async ({page}) => {
|
|||
await expect(page.locator('svg.gitea-gitbucket')).toBeVisible();
|
||||
await expect(page.locator('svg.gitea-codebase')).toBeVisible();
|
||||
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('Migration Repo Name detection', async ({page}, workerInfo) => {
|
||||
|
@ -50,7 +51,7 @@ test('Migration Repo Name detection', async ({page}, workerInfo) => {
|
|||
await expect(form.getByRole('textbox', {name: 'Repository Name'})).toHaveValue('test');
|
||||
|
||||
// Save screenshot only once
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('Migration Progress Page', async ({page, browser}, workerInfo) => {
|
||||
|
@ -64,10 +65,10 @@ test('Migration Progress Page', async ({page, browser}, workerInfo) => {
|
|||
const form = page.locator('form');
|
||||
await form.getByRole('textbox', {name: 'Repository Name'}).fill(repoName);
|
||||
await form.getByRole('textbox', {name: 'Migrate / Clone from URL'}).fill(`https://codeberg.org/forgejo/${repoName}`);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await form.locator('button.primary').click({timeout: 5000});
|
||||
await expect(page).toHaveURL(`user2/${repoName}`);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
const ctx = await test_context(browser, {storageState: {cookies: [], origins: []}});
|
||||
const unauthenticatedPage = await ctx.newPage();
|
||||
|
@ -76,13 +77,13 @@ test('Migration Progress Page', async ({page, browser}, workerInfo) => {
|
|||
|
||||
await page.reload();
|
||||
await expect(page.locator('#repo_migrating_failed')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await page.getByRole('button', {name: 'Delete this repository'}).click();
|
||||
const deleteModal = page.locator('#delete-repo-modal');
|
||||
await deleteModal.getByRole('textbox', {name: 'Confirmation string'}).fill(`user2/${repoName}`);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await deleteModal.getByRole('button', {name: 'Delete repository'}).click();
|
||||
await expect(page).toHaveURL('/');
|
||||
// checked last to preserve the order of screenshots from first run
|
||||
await save_visual(unauthenticatedPage);
|
||||
await screenshot(unauthenticatedPage);
|
||||
});
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {test, dynamic_id, save_visual} from './utils_e2e.ts';
|
||||
import {test, dynamic_id} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
import {validate_form} from './shared/forms.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
@ -17,12 +18,12 @@ test('New repo: invalid', async ({page}) => {
|
|||
await expect(page.getByText('.gitignore Select .gitignore')).toBeHidden();
|
||||
await expect(page.getByText('Labels Select a label set')).toBeHidden();
|
||||
await validate_form({page}, 'fieldset');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
await page.getByLabel('Repository name').fill('*invalid');
|
||||
await page.getByRole('button', {name: 'Create repository'}).click();
|
||||
await expect(page.getByText('Repository name should contain only alphanumeric')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('New repo: initialize', async ({page}, workerInfo) => {
|
||||
|
@ -46,7 +47,7 @@ test('New repo: initialize', async ({page}, workerInfo) => {
|
|||
await page.getByLabel('Make repository a template').check();
|
||||
|
||||
await validate_form({page}, 'fieldset');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
const reponame = dynamic_id();
|
||||
await page.getByLabel('Repository name').fill(reponame);
|
||||
await page.getByRole('button', {name: 'Create repository'}).click();
|
||||
|
@ -55,7 +56,7 @@ test('New repo: initialize', async ({page}, workerInfo) => {
|
|||
if (!workerInfo.project.name.includes('Mobile')) {
|
||||
await expect(page.getByText('Template', {exact: true})).toBeVisible();
|
||||
}
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('New repo: initialize later', async ({page}) => {
|
||||
|
@ -72,7 +73,7 @@ test('New repo: initialize later', async ({page}) => {
|
|||
expect(page.url()).toBe(`http://localhost:3003/user2/${reponame}`);
|
||||
await expect(page.getByRole('link', {name: 'New file'})).toBeVisible();
|
||||
await expect(page.getByRole('heading', {name: 'Creating a new repository on'})).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
// add a README
|
||||
await page.getByRole('link', {name: 'New file'}).click();
|
||||
|
@ -89,7 +90,7 @@ test('New repo: initialize later', async ({page}) => {
|
|||
expect(page.url()).toBe(`http://localhost:3003/user2/${reponame}/src/branch/devbranch/README.md`);
|
||||
await expect(page.getByRole('link', {name: 'My first commit message'})).toBeVisible();
|
||||
await expect(page.getByText('Hello Forgejo!')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('New repo: from template', async ({page}, workerInfo) => {
|
||||
|
@ -101,11 +102,11 @@ test('New repo: from template', async ({page}, workerInfo) => {
|
|||
await page.getByRole('group', {name: 'Use a template You can select'}).getByRole('combobox').click();
|
||||
await page.getByRole('option', {name: 'user27/template1'}).click();
|
||||
await page.getByText('Git content (Default branch)').click();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await page.getByLabel('Repository name').fill(reponame);
|
||||
await page.getByRole('button', {name: 'Create repository'}).click();
|
||||
await expect(page.getByRole('link', {name: `${reponame}.log`})).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('New repo: label set', async ({page}) => {
|
||||
|
@ -117,13 +118,13 @@ test('New repo: label set', async ({page}) => {
|
|||
await page.getByRole('option', {name: 'Advanced (Kind/Bug, Kind/'}).click();
|
||||
// close dropdown via unrelated click
|
||||
await page.getByText('You can select an existing').click();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await page.getByLabel('Repository name').fill(reponame);
|
||||
await page.getByRole('button', {name: 'Create repository'}).click();
|
||||
await page.goto(`/user2/${reponame}/issues`);
|
||||
await page.getByRole('link', {name: 'Labels'}).click();
|
||||
await expect(page.getByText('Kind/Bug Something is not')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('New repo: gitignore', async ({page}) => {
|
||||
|
@ -140,7 +141,7 @@ test('New repo: gitignore', async ({page}) => {
|
|||
await page.getByRole('option', {name: 'NotesAndExtendedConfiguration'}).click();
|
||||
await page.getByRole('option', {name: 'MetaProgrammingSystem'}).click();
|
||||
await page.getByRole('option', {name: 'AppceleratorTitanium'}).click();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
const segmentWidth = (await page.locator('.segment').boundingBox()).width;
|
||||
const dropdownWidth = (await gitignoreDropdown.boundingBox()).width;
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {test, save_visual} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
import {validate_form} from './shared/forms.ts';
|
||||
|
||||
test.use({user: 'user2'});
|
||||
|
@ -21,13 +22,13 @@ test('repo webhook settings', async ({page}) => {
|
|||
|
||||
// check accessibility including the custom events (now visible) part
|
||||
await validate_form({page}, 'fieldset');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
await page.locator('input[name="events"][value="push_only"]').click();
|
||||
await expect(page.locator('.hide-unless-checked')).toBeHidden();
|
||||
await page.locator('input[name="events"][value="send_everything"]').click();
|
||||
await expect(page.locator('.hide-unless-checked')).toBeHidden();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test.describe('repo branch protection settings', () => {
|
||||
|
@ -53,11 +54,11 @@ test.describe('repo branch protection settings', () => {
|
|||
// verify header is new
|
||||
await expect(page.locator('h4')).toContainText('new');
|
||||
await page.locator('input[name="rule_name"]').fill('testrule');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await page.locator('button:text("Save rule")').click();
|
||||
// verify header is in edit mode
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
// find the edit button and click it
|
||||
const editButton = page.locator('a[href="/user2/repo1/settings/branches/edit?rule_name=testrule"]');
|
||||
|
@ -65,6 +66,6 @@ test.describe('repo branch protection settings', () => {
|
|||
|
||||
await page.waitForLoadState();
|
||||
await expect(page.locator('.repo-setting-content .header')).toContainText('Protection rules for branch', {ignoreCase: true, useInnerText: true});
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
for (const searchTerm of ['space', 'consectetur']) {
|
||||
for (const width of [null, 2560, 4000]) {
|
||||
|
@ -25,7 +26,7 @@ for (const searchTerm of ['space', 'consectetur']) {
|
|||
await expect(page.locator('#wiki-search a[href]')).toBeInViewport({
|
||||
ratio: workerInfo.project.name === 'webkit' ? 0.9 : 1,
|
||||
});
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -39,12 +40,12 @@ test(`Search results show titles (and not file names)`, async ({page}, workerInf
|
|||
// so we manually "type" the last letter
|
||||
await page.getByPlaceholder('Search wiki').dispatchEvent('keyup');
|
||||
await expect(page.locator('#wiki-search a[href] b')).toHaveText('Page With Spaced Name');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('Wiki unicode-escape', async ({page}) => {
|
||||
await page.goto('/user2/unicode-escaping/wiki');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
expect(await page.locator('.ui.message.unicode-escape-prompt').count()).toEqual(3);
|
||||
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {save_visual, test} from './utils_e2e.ts';
|
||||
import {test} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test.describe('desktop viewport as user 2', () => {
|
||||
test.use({user: 'user2', viewport: {width: 1920, height: 300}});
|
||||
|
@ -54,7 +55,7 @@ test.describe('desktop viewport, unauthenticated', () => {
|
|||
await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0);
|
||||
|
||||
await expect(page.locator('.overflow-menu-button')).toHaveCount(0);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -79,7 +80,7 @@ test.describe('small viewport', () => {
|
|||
|
||||
const items = shownItems.concat(overflowItems);
|
||||
expect(Array.from(new Set(items))).toHaveLength(items.length);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('Settings button in overflow menu of org header', async ({page}) => {
|
||||
|
@ -123,6 +124,6 @@ test.describe('small viewport, unauthenticated', () => {
|
|||
|
||||
const items = shownItems.concat(overflowItems);
|
||||
expect(Array.from(new Set(items))).toHaveLength(items.length);
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
});
|
||||
|
|
89
tests/e2e/shared/screenshots.ts
Normal file
89
tests/e2e/shared/screenshots.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
import {expect, type Page, type Locator} from '@playwright/test';
|
||||
|
||||
// returns element that should be covered before taking the screenshot
|
||||
async function masks(page: Page) : Array<Locator> {
|
||||
return [
|
||||
page.locator('.ui.avatar'),
|
||||
page.locator('.sha'),
|
||||
page.locator('#repo_migrating'),
|
||||
// update order of recently created repos is not fully deterministic
|
||||
page.locator('.flex-item-main').filter({hasText: 'relative time in repo'}),
|
||||
page.locator('#activity-feed'),
|
||||
page.locator('#user-heatmap'),
|
||||
// dynamic IDs in fixed-size inputs
|
||||
page.locator('input[value*="dyn-id-"]'),
|
||||
];
|
||||
}
|
||||
|
||||
// replaces elements on the page that cause flakiness
|
||||
async function screenshot_prepare(page: Page) {
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
// Version string is dynamic
|
||||
await page.locator('footer .left-links').evaluate((node) => node.innerHTML = 'MOCK');
|
||||
|
||||
// replace timestamps in repos to mask them later down
|
||||
await page.locator('.flex-item-body > relative-time').filter({hasText: /now|minute/}).evaluateAll((nodes) => {
|
||||
for (const node of nodes) node.outerHTML = 'relative time in repo';
|
||||
});
|
||||
// other time elements
|
||||
await page.locator('relative-time').evaluateAll((nodes) => {
|
||||
for (const node of nodes) node.outerHTML = 'time element';
|
||||
});
|
||||
await page.locator('absolute-date').evaluateAll((nodes) => {
|
||||
for (const node of nodes) node.outerHTML = 'time element';
|
||||
});
|
||||
|
||||
// dynamically generated UUIDs
|
||||
await page.getByText('dyn-id-').evaluateAll((nodes) => {
|
||||
for (const node of nodes) node.innerHTML = node.innerHTML.replaceAll(/dyn-id-[a-f0-9-]+/g, 'dynamic-id');
|
||||
});
|
||||
// repeat above, work around https://github.com/microsoft/playwright/issues/34152
|
||||
await page.getByText('dyn-id-').evaluateAll((nodes) => {
|
||||
for (const node of nodes) node.innerHTML = node.innerHTML.replaceAll(/dyn-id-[a-f0-9-]+/g, 'dynamic-id');
|
||||
});
|
||||
|
||||
// attachment IDs in text areas, required for issue-comment-dropzone.
|
||||
// playwright does not (yet?) support filtering for content in input elements, see https://github.com/microsoft/playwright/issues/36166
|
||||
await page.locator('textarea.markdown-text-editor').evaluateAll((nodes) => {
|
||||
for (const node of nodes) node.value = node.value.replaceAll(/attachments\/[a-f0-9-]+/g, '/attachments/c1ee9740-dad3-4747-b489-f6fb2e3dfcec');
|
||||
});
|
||||
|
||||
// dynamically created test users
|
||||
await page.getByText('e2e-test-').evaluateAll((nodes) => {
|
||||
for (const node of nodes) node.innerHTML = node.innerHTML.replaceAll(/e2e-test-[0-9-]+/g, 'e2e-test-user');
|
||||
});
|
||||
}
|
||||
|
||||
export async function screenshot(page: Page, locator?: Locator, margin = 0) {
|
||||
// Optionally include visual testing
|
||||
if (process.env.VISUAL_TEST) {
|
||||
await screenshot_prepare(page);
|
||||
if (locator === undefined) {
|
||||
await screenshot_full(page);
|
||||
} else {
|
||||
await screenshot_selective(page, locator, margin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function screenshot_selective(page: Page, locator: Locator, margin: number) {
|
||||
const clip = await locator.boundingBox();
|
||||
clip.x = Math.max(clip.x - margin, 0);
|
||||
clip.y = Math.max(clip.y - margin, 0);
|
||||
clip.width += margin * 2;
|
||||
clip.height += margin * 2;
|
||||
await expect(page).toHaveScreenshot({
|
||||
fullPage: true,
|
||||
timeout: 20000,
|
||||
clip,
|
||||
mask: await masks(page),
|
||||
});
|
||||
}
|
||||
|
||||
async function screenshot_full(page: Page) {
|
||||
await expect(page).toHaveScreenshot({
|
||||
fullPage: true,
|
||||
timeout: 20000,
|
||||
mask: await masks(page),
|
||||
});
|
||||
}
|
|
@ -4,7 +4,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {test, save_visual, login_user, login} from './utils_e2e.ts';
|
||||
import {test, login_user, login} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
import {validate_form} from './shared/forms.ts';
|
||||
|
||||
test.beforeAll(async ({browser}, workerInfo) => {
|
||||
|
@ -36,12 +37,12 @@ test('User: Profile settings', async ({browser}, workerInfo) => {
|
|||
await page.getByLabel('Hide activity from profile').check();
|
||||
|
||||
await validate_form({page}, 'fieldset');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await page.getByRole('button', {name: 'Update profile'}).click();
|
||||
await expect(page.getByText('Your profile has been updated.')).toBeVisible();
|
||||
await page.getByRole('link', {name: 'public activity'}).click();
|
||||
await expect(page.getByText('Your activity is only visible')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
await page.goto('/user2');
|
||||
await expect(page.getByText('SecondUser')).toBeVisible();
|
||||
|
@ -49,7 +50,7 @@ test('User: Profile settings', async ({browser}, workerInfo) => {
|
|||
await expect(page.locator('li').filter({hasText: 'user2@example.com'})).toBeVisible();
|
||||
await expect(page.locator('li').filter({hasText: 'https://forgejo.org'})).toBeVisible();
|
||||
await expect(page.getByText('I am a playwright test')).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
await page.goto('/user/settings');
|
||||
await page.locator('input[list="pronouns"]').fill('rob/ot');
|
||||
|
@ -59,7 +60,7 @@ test('User: Profile settings', async ({browser}, workerInfo) => {
|
|||
await page.getByLabel('Hide activity from profile').uncheck();
|
||||
await expect(page.getByText('Your profile has been updated.')).toBeHidden();
|
||||
await validate_form({page}, 'fieldset');
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
await page.getByRole('button', {name: 'Update profile'}).click();
|
||||
await expect(page.getByText('Your profile has been updated.')).toBeVisible();
|
||||
|
||||
|
@ -84,13 +85,13 @@ test('User: Storage overview', async ({browser}, workerInfo) => {
|
|||
await page.locator('.stats summary').nth(1).click();
|
||||
await expect(page.locator('.stats ul').nth(1)).toBeVisible();
|
||||
await expect(page.getByText('Git LFS 8 KiB').nth(1)).toBeVisible();
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
|
||||
await page.locator('.stats summary').nth(1).click();
|
||||
await expect(page.locator('.stats ul').nth(1)).toBeHidden();
|
||||
await expect(page.getByText('Git LFS 8 KiB').nth(1)).toBeHidden();
|
||||
|
||||
await save_visual(page);
|
||||
await screenshot(page);
|
||||
});
|
||||
|
||||
test('User: Canceling adding SSH key clears inputs', async ({browser}, workerInfo) => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {expect, test as baseTest, type Browser, type BrowserContextOptions, type APIRequestContext, type TestInfo, type Page} from '@playwright/test';
|
||||
import {expect, test as baseTest, type Browser, type BrowserContextOptions, type APIRequestContext, type TestInfo} from '@playwright/test';
|
||||
|
||||
import * as path from 'node:path';
|
||||
|
||||
|
@ -84,49 +84,6 @@ export async function login({browser}: {browser: Browser}, workerInfo: TestInfo)
|
|||
return await context?.newPage();
|
||||
}
|
||||
|
||||
export async function save_visual(page: Page) {
|
||||
// Optionally include visual testing
|
||||
if (process.env.VISUAL_TEST) {
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
// Mock/replace dynamic content which can have different size (and thus cannot simply be masked below)
|
||||
await page.locator('footer .left-links').evaluate((node) => node.innerHTML = 'MOCK');
|
||||
// replace timestamps in repos to mask them later down
|
||||
await page.locator('.flex-item-body > relative-time').filter({hasText: /now|minute/}).evaluateAll((nodes) => {
|
||||
for (const node of nodes) node.outerHTML = 'relative time in repo';
|
||||
});
|
||||
// dynamically generated UUIDs
|
||||
await page.getByText('dyn-id-').evaluateAll((nodes) => {
|
||||
for (const node of nodes) node.innerHTML = node.innerHTML.replaceAll(/dyn-id-[a-f0-9-]+/g, 'dynamic-id');
|
||||
});
|
||||
// repeat above, work around https://github.com/microsoft/playwright/issues/34152
|
||||
await page.getByText('dyn-id-').evaluateAll((nodes) => {
|
||||
for (const node of nodes) node.innerHTML = node.innerHTML.replaceAll(/dyn-id-[a-f0-9-]+/g, 'dynamic-id');
|
||||
});
|
||||
await page.locator('relative-time').evaluateAll((nodes) => {
|
||||
for (const node of nodes) node.outerHTML = 'time element';
|
||||
});
|
||||
// used for instance for security keys
|
||||
await page.locator('absolute-date').evaluateAll((nodes) => {
|
||||
for (const node of nodes) node.outerHTML = 'time element';
|
||||
});
|
||||
await expect(page).toHaveScreenshot({
|
||||
fullPage: true,
|
||||
timeout: 20000,
|
||||
mask: [
|
||||
page.locator('.ui.avatar'),
|
||||
page.locator('.sha'),
|
||||
page.locator('#repo_migrating'),
|
||||
// update order of recently created repos is not fully deterministic
|
||||
page.locator('.flex-item-main').filter({hasText: 'relative time in repo'}),
|
||||
page.locator('#activity-feed'),
|
||||
page.locator('#user-heatmap'),
|
||||
// dynamic IDs in fixed-size inputs
|
||||
page.locator('input[value*="dyn-id-"]'),
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create a temporary user and login to that user and store session info.
|
||||
// This should ideally run on a per test basis.
|
||||
export async function create_temp_user(browser: Browser, workerInfo: TestInfo, request: APIRequestContext) {
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
// @watch end
|
||||
|
||||
import {expect} from '@playwright/test';
|
||||
import {test, save_visual, create_temp_user, login_user} from './utils_e2e.ts';
|
||||
import {test, create_temp_user, login_user} from './utils_e2e.ts';
|
||||
import {screenshot} from './shared/screenshots.ts';
|
||||
|
||||
test('WebAuthn register & login flow', async ({browser, request}, workerInfo) => {
|
||||
test.skip(workerInfo.project.name !== 'chromium', 'Uses Chrome protocol');
|
||||
|
@ -34,7 +35,7 @@ test('WebAuthn register & login flow', async ({browser, request}, workerInfo) =>
|
|||
});
|
||||
|
||||
await page.locator('input#nickname').fill('Testing Security Key');
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.user-setting-content'));
|
||||
await page.getByText('Add security key').click();
|
||||
|
||||
// Logout.
|
||||
|
@ -57,7 +58,7 @@ test('WebAuthn register & login flow', async ({browser, request}, workerInfo) =>
|
|||
response = await page.goto('/user/settings/security');
|
||||
expect(response?.status()).toBe(200);
|
||||
await page.getByRole('button', {name: 'Remove'}).click();
|
||||
await save_visual(page);
|
||||
await screenshot(page, page.locator('.ui.g-modal-confirm.delete.modal'), 50);
|
||||
await page.getByRole('button', {name: 'Yes'}).click();
|
||||
await page.waitForLoadState();
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue