chore(e2e): simplify authentication setup (#6400)
Some checks are pending
/ release (push) Waiting to run
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions

Replaced manual login and context loading across tests with Playwright's `test.use` configuration for user authentication. This simplifies test setup, improves readability, and reduces repetition.

For #6362

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6400
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Julian Schlarb <julian.schlarb@denktmit.de>
Co-committed-by: Julian Schlarb <julian.schlarb@denktmit.de>
This commit is contained in:
Julian Schlarb 2025-01-05 05:17:04 +00:00 committed by Gusted
parent a2eb249766
commit 68d690b6b9
19 changed files with 327 additions and 254 deletions

1
.gitignore vendored
View file

@ -73,6 +73,7 @@ cpu.out
/tests/e2e/reports /tests/e2e/reports
/tests/e2e/test-artifacts /tests/e2e/test-artifacts
/tests/e2e/test-snapshots /tests/e2e/test-snapshots
/tests/e2e/.auth
/tests/*.ini /tests/*.ini
/tests/**/*.git/**/*.sample /tests/**/*.git/**/*.sample
/node_modules /node_modules

View file

@ -250,16 +250,18 @@ test('For anyone', async ({page}) => {
If you need a user account, you can use something like: If you need a user account, you can use something like:
~~~js ~~~js
import {test, login_user, login} from './utils_e2e.ts'; import {test} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { // reuse user2 token from scope `shared`
await login_user(browser, workerInfo, 'user2'); // or another user test.use({user: 'user2', authScope: 'shared'})
});
test('For signed users only', async ({browser}, workerInfo) => { test('For signed users only', async ({page}) => {
const page = await login({browser}, workerInfo);
})
~~~ ~~~
users are created in [utils_e2e_test.go](utils_e2e_test.go)
### Run tests very selectively ### Run tests very selectively
Browser testing can take some time. Browser testing can take some time.

View file

@ -10,18 +10,13 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, save_visual, load_logged_in_context} from './utils_e2e.ts'; import {save_visual, test} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2');
});
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.describe('Workflow Authenticated user2', () => {
test.use({user: 'user2'});
test('workflow dispatch present', async ({browser}, workerInfo) => { test('workflow dispatch present', async ({page}) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const page = await context.newPage();
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 expect(page.getByText(workflow_trigger_notification_text)).toBeVisible(); await expect(page.getByText(workflow_trigger_notification_text)).toBeVisible();
@ -34,13 +29,10 @@ test('workflow dispatch present', async ({browser}, workerInfo) => {
await run_workflow_btn.click(); await run_workflow_btn.click();
await expect(menu).toBeVisible(); await expect(menu).toBeVisible();
await save_visual(page); await save_visual(page);
}); });
test('workflow dispatch error: missing inputs', async ({browser}, workerInfo) => { test('dispatch error: missing inputs', async ({page}, testInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Flaky behaviour on mobile safari; see https://codeberg.org/forgejo/forgejo/pulls/3334#issuecomment-2033383'); test.skip(testInfo.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 page = await context.newPage();
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');
@ -56,14 +48,10 @@ test('workflow dispatch error: missing inputs', async ({browser}, workerInfo) =>
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();
await save_visual(page); await save_visual(page);
}); });
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');
const page = await context.newPage();
test('dispatch success', async ({page}, testInfo) => {
test.skip(testInfo.project.name === 'Mobile Safari', 'Flaky behaviour on mobile safari; see https://codeberg.org/forgejo/forgejo/pulls/3334#issuecomment-2033383');
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.locator('#workflow_dispatch_dropdown>button').click(); await page.locator('#workflow_dispatch_dropdown>button').click();
@ -76,6 +64,7 @@ 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();
await save_visual(page); await save_visual(page);
});
}); });
test('workflow dispatch box not available for unauthenticated users', async ({page}) => { test('workflow dispatch box not available for unauthenticated users', async ({page}) => {

View file

@ -3,21 +3,24 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, save_visual, load_logged_in_context} from './utils_e2e.ts'; import {save_visual, test} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.use({user: 'user2'});
await login_user(browser, workerInfo, 'user2');
});
test('Correct link and tooltip', async ({browser}, workerInfo) => { test.describe.configure({retries: 2});
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const page = await context.newPage(); test('Correct link and tooltip', async ({page}, testInfo) => {
if (testInfo.retry) {
await page.goto('/user2/test_workflows/actions');
}
const searchResponse = page.waitForResponse((resp) => resp.url().includes('/repo/search?') && resp.status() === 200);
const response = await page.goto('/?repo-search-query=test_workflows'); const response = await page.goto('/?repo-search-query=test_workflows');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);
await searchResponse;
const repoStatus = page.locator('.dashboard-repos .repo-owner-name-list > li:nth-child(1) > a:nth-child(2)'); const repoStatus = page.locator('.dashboard-repos .repo-owner-name-list > li:nth-child(1) > a:nth-child(2)');
// wait for network activity to cease (so status was loaded in frontend)
await page.waitForLoadState('networkidle'); // eslint-disable-line playwright/no-networkidle
await expect(repoStatus).toHaveAttribute('href', '/user2/test_workflows/actions', {timeout: 10000}); await expect(repoStatus).toHaveAttribute('href', '/user2/test_workflows/actions', {timeout: 10000});
await expect(repoStatus).toHaveAttribute('data-tooltip-content', /^(Error|Failure)$/); await expect(repoStatus).toHaveAttribute('data-tooltip-content', /^(Error|Failure)$/);
await save_visual(page); await save_visual(page);

View file

@ -1,14 +1,10 @@
// @ts-check // @ts-check
import {test, expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {login_user, save_visual, load_logged_in_context} from './utils_e2e.ts'; import {save_visual, test} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.use({user: 'user2'});
await login_user(browser, workerInfo, 'user2');
});
test('Change git note', async ({browser}, workerInfo) => { test('Change git note', async ({page}) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const page = await context.newPage();
let response = await page.goto('/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d'); let response = await page.goto('/user2/repo1/commit/65f1bf27bc3bf70f64657658635e66094edbcb4d');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);

View file

@ -5,14 +5,11 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, save_visual, login_user, login} from './utils_e2e.ts'; import {test, save_visual} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.use({user: 'user2'});
await login_user(browser, workerInfo, 'user2');
});
test('Menu accessibility', async ({browser}, workerInfo) => { test('Menu accessibility', async ({page}) => {
const page = await login({browser}, workerInfo);
await page.goto('/user2/repo1/issues/1'); await page.goto('/user2/repo1/issues/1');
await expect(page.getByLabel('user2 reacted eyes. Remove eyes')).toBeVisible(); await expect(page.getByLabel('user2 reacted eyes. Remove eyes')).toBeVisible();
await expect(page.getByLabel('reacted laugh. Remove laugh')).toBeVisible(); await expect(page.getByLabel('reacted laugh. Remove laugh')).toBeVisible();
@ -24,9 +21,8 @@ test('Menu accessibility', async ({browser}, workerInfo) => {
await expect(page.getByLabel('user1, user2 reacted laugh. Remove laugh')).toBeVisible(); await expect(page.getByLabel('user1, user2 reacted laugh. Remove laugh')).toBeVisible();
}); });
test('Hyperlink paste behaviour', async ({browser}, workerInfo) => { test('Hyperlink paste behaviour', async ({page}, workerInfo) => {
test.skip(['Mobile Safari', 'Mobile Chrome', 'webkit'].includes(workerInfo.project.name), 'Mobile clients seem to have very weird behaviour with this test, which I cannot confirm with real usage'); test.skip(['Mobile Safari', 'Mobile Chrome', 'webkit'].includes(workerInfo.project.name), 'Mobile clients seem to have very weird behaviour with this test, which I cannot confirm with real usage');
const page = await login({browser}, workerInfo);
await page.goto('/user2/repo1/issues/new'); await page.goto('/user2/repo1/issues/new');
await page.locator('textarea').click(); await page.locator('textarea').click();
// same URL // same URL
@ -58,8 +54,7 @@ test('Hyperlink paste behaviour', async ({browser}, workerInfo) => {
await page.locator('textarea').fill(''); await page.locator('textarea').fill('');
}); });
test('Always focus edit tab first on edit', async ({browser}, workerInfo) => { test('Always focus edit tab first on edit', async ({page}) => {
const page = await login({browser}, workerInfo);
const response = await page.goto('/user2/repo1/issues/1'); const response = await page.goto('/user2/repo1/issues/1');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);
@ -82,9 +77,8 @@ test('Always focus edit tab first on edit', async ({browser}, workerInfo) => {
await save_visual(page); await save_visual(page);
}); });
test('Quote reply', async ({browser}, workerInfo) => { test('Quote reply', async ({page}, workerInfo) => {
test.skip(workerInfo.project.name !== 'firefox', 'Uses Firefox specific selection quirks'); test.skip(workerInfo.project.name !== 'firefox', 'Uses Firefox specific selection quirks');
const page = await login({browser}, workerInfo);
const response = await page.goto('/user2/repo1/issues/1'); const response = await page.goto('/user2/repo1/issues/1');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);
@ -157,9 +151,8 @@ test('Quote reply', async ({browser}, workerInfo) => {
await editorTextarea.fill(''); await editorTextarea.fill('');
}); });
test('Pull quote reply', async ({browser}, workerInfo) => { test('Pull quote reply', async ({page}, workerInfo) => {
test.skip(workerInfo.project.name !== 'firefox', 'Uses Firefox specific selection quirks'); test.skip(workerInfo.project.name !== 'firefox', 'Uses Firefox specific selection quirks');
const page = await login({browser}, workerInfo);
const response = await page.goto('/user2/commitsonpr/pulls/1/files'); const response = await page.goto('/user2/commitsonpr/pulls/1/files');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);

View file

@ -7,14 +7,13 @@
/* eslint playwright/expect-expect: ["error", { "assertFunctionNames": ["check_wip"] }] */ /* eslint playwright/expect-expect: ["error", { "assertFunctionNames": ["check_wip"] }] */
import {expect, type Page} from '@playwright/test'; import {expect, type Page} from '@playwright/test';
import {test, save_visual, login_user, login} from './utils_e2e.ts'; import {save_visual, test} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.use({user: 'user2'});
await login_user(browser, workerInfo, 'user2');
});
test.describe('Pull: Toggle WIP', () => { test.describe('Pull: Toggle WIP', () => {
const prTitle = 'pull5'; const prTitle = 'pull5';
async function toggle_wip_to({page}, should: boolean) { async function toggle_wip_to({page}, should: boolean) {
await page.waitForLoadState('domcontentloaded'); await page.waitForLoadState('domcontentloaded');
if (should) { if (should) {
@ -39,8 +38,7 @@ test.describe('Pull: Toggle WIP', () => {
} }
} }
test.beforeEach(async ({browser}, workerInfo) => { test.beforeEach(async ({page}) => {
const page = await login({browser}, workerInfo);
const response = await page.goto('/user2/repo1/pulls/5'); const response = await page.goto('/user2/repo1/pulls/5');
expect(response?.status()).toBe(200); // Status OK expect(response?.status()).toBe(200); // Status OK
// ensure original title // ensure original title
@ -50,9 +48,8 @@ test.describe('Pull: Toggle WIP', () => {
await check_wip({page}, false); await check_wip({page}, false);
}); });
test('simple toggle', async ({browser}, workerInfo) => { test('simple toggle', async ({page}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636');
const page = await login({browser}, workerInfo);
await page.goto('/user2/repo1/pulls/5'); await page.goto('/user2/repo1/pulls/5');
// toggle to WIP // toggle to WIP
await toggle_wip_to({page}, true); await toggle_wip_to({page}, true);
@ -62,9 +59,8 @@ test.describe('Pull: Toggle WIP', () => {
await check_wip({page}, false); await check_wip({page}, false);
}); });
test('manual edit', async ({browser}, workerInfo) => { test('manual edit', async ({page}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636');
const page = await login({browser}, workerInfo);
await page.goto('/user2/repo1/pulls/5'); await page.goto('/user2/repo1/pulls/5');
// manually edit title to another prefix // manually edit title to another prefix
await page.locator('#issue-title-edit-show').click(); await page.locator('#issue-title-edit-show').click();
@ -76,9 +72,8 @@ test.describe('Pull: Toggle WIP', () => {
await check_wip({page}, false); await check_wip({page}, false);
}); });
test('maximum title length', async ({browser}, workerInfo) => { test('maximum title length', async ({page}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636');
const page = await login({browser}, workerInfo);
await page.goto('/user2/repo1/pulls/5'); await page.goto('/user2/repo1/pulls/5');
// check maximum title length is handled gracefully // check maximum title length is handled gracefully
const maxLenStr = prTitle + 'a'.repeat(240); const maxLenStr = prTitle + 'a'.repeat(240);
@ -96,17 +91,16 @@ test.describe('Pull: Toggle WIP', () => {
}); });
}); });
test('Issue: Labels', async ({browser}, workerInfo) => { test('Issue: Labels', async ({page}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636');
async function submitLabels({page}: {page: Page}) { async function submitLabels({page}: { page: Page }) {
const submitted = page.waitForResponse('/user2/repo1/issues/labels'); const submitted = page.waitForResponse('/user2/repo1/issues/labels');
await page.locator('textarea').first().click(); // close via unrelated element await page.locator('textarea').first().click(); // close via unrelated element
await submitted; await submitted;
await page.waitForLoadState(); await page.waitForLoadState();
} }
const page = await login({browser}, workerInfo);
// select label list in sidebar only // select label list in sidebar only
const labelList = page.locator('.issue-content-right .labels-list a'); const labelList = page.locator('.issue-content-right .labels-list a');
const response = await page.goto('/user2/repo1/issues/1'); const response = await page.goto('/user2/repo1/issues/1');
@ -144,9 +138,8 @@ test('Issue: Labels', async ({browser}, workerInfo) => {
await expect(labelList.filter({hasText: 'label1'})).toBeVisible(); await expect(labelList.filter({hasText: 'label1'})).toBeVisible();
}); });
test('Issue: Assignees', async ({browser}, workerInfo) => { test('Issue: Assignees', async ({page}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636');
const page = await login({browser}, workerInfo);
// select label list in sidebar only // select label list in sidebar only
const assigneesList = page.locator('.issue-content-right .assignees.list .selected .item a'); const assigneesList = page.locator('.issue-content-right .assignees.list .selected .item a');
@ -182,9 +175,8 @@ test('Issue: Assignees', async ({browser}, workerInfo) => {
await expect(page.locator('.ui.assignees.list .item.no-select')).toBeHidden(); await expect(page.locator('.ui.assignees.list .item.no-select')).toBeHidden();
}); });
test('New Issue: Assignees', async ({browser}, workerInfo) => { test('New Issue: Assignees', async ({page}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636');
const page = await login({browser}, workerInfo);
// select label list in sidebar only // select label list in sidebar only
const assigneesList = page.locator('.issue-content-right .assignees.list .selected .item'); const assigneesList = page.locator('.issue-content-right .assignees.list .selected .item');
@ -224,9 +216,8 @@ test('New Issue: Assignees', async ({browser}, workerInfo) => {
await save_visual(page); await save_visual(page);
}); });
test('Issue: Milestone', async ({browser}, workerInfo) => { test('Issue: Milestone', async ({page}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636');
const page = await login({browser}, workerInfo);
const response = await page.goto('/user2/repo1/issues/1'); const response = await page.goto('/user2/repo1/issues/1');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);
@ -248,9 +239,8 @@ test('Issue: Milestone', async ({browser}, workerInfo) => {
await expect(page.locator('.timeline-item.event').last()).toContainText('user2 removed this from the milestone1 milestone'); await expect(page.locator('.timeline-item.event').last()).toContainText('user2 removed this from the milestone1 milestone');
}); });
test('New Issue: Milestone', async ({browser}, workerInfo) => { test('New Issue: Milestone', async ({page}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636'); test.skip(workerInfo.project.name === 'Mobile Safari', 'Unable to get tests working on Safari Mobile, see https://codeberg.org/forgejo/forgejo/pulls/3445#issuecomment-1789636');
const page = await login({browser}, workerInfo);
const response = await page.goto('/user2/repo1/issues/new'); const response = await page.goto('/user2/repo1/issues/new');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);

View file

@ -5,21 +5,16 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, save_visual, load_logged_in_context, login_user} from './utils_e2e.ts'; import {save_visual, test} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.use({user: 'user2'});
await login_user(browser, workerInfo, 'user2');
});
test('Markdown image preview behaviour', async ({browser}, workerInfo) => { test('Markdown image preview behaviour', async ({page}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Flaky behaviour on mobile safari;'); test.skip(workerInfo.project.name === 'Mobile Safari', 'Flaky behaviour on mobile safari;');
const context = await load_logged_in_context(browser, workerInfo, 'user2');
// Editing the root README.md file for image preview // Editing the root README.md file for image preview
const editPath = '/user2/repo1/src/branch/master/README.md'; const editPath = '/user2/repo1/src/branch/master/README.md';
const page = await context.newPage();
const response = await page.goto(editPath, {waitUntil: 'domcontentloaded'}); const response = await page.goto(editPath, {waitUntil: 'domcontentloaded'});
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);
@ -43,12 +38,9 @@ test('Markdown image preview behaviour', async ({browser}, workerInfo) => {
await save_visual(page); await save_visual(page);
}); });
test('markdown indentation', async ({browser}, workerInfo) => { test('markdown indentation', async ({page}) => {
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`;
const page = await context.newPage();
const response = await page.goto('/user2/repo1/issues/new'); const response = await page.goto('/user2/repo1/issues/new');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);
@ -116,12 +108,9 @@ test('markdown indentation', async ({browser}, workerInfo) => {
await expect(textarea).toHaveValue(initText); await expect(textarea).toHaveValue(initText);
}); });
test('markdown list continuation', async ({browser}, workerInfo) => { test('markdown list continuation', async ({page}) => {
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`;
const page = await context.newPage();
const response = await page.goto('/user2/repo1/issues/new'); const response = await page.goto('/user2/repo1/issues/new');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);
@ -213,10 +202,7 @@ test('markdown list continuation', async ({browser}, workerInfo) => {
} }
}); });
test('markdown insert table', async ({browser}, workerInfo) => { test('markdown insert table', async ({page}) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const page = await context.newPage();
const response = await page.goto('/user2/repo1/issues/new'); const response = await page.goto('/user2/repo1/issues/new');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);

View file

@ -5,16 +5,13 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, save_visual, login_user, login} from './utils_e2e.ts'; import {save_visual, test} from './utils_e2e.ts';
import {validate_form} from './shared/forms.ts'; import {validate_form} from './shared/forms.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.use({user: 'user2'});
await login_user(browser, workerInfo, 'user2');
});
test('org team settings', async ({browser}, workerInfo) => { test('org team settings', async ({page}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Cannot get it to work - as usual'); test.skip(workerInfo.project.name === 'Mobile Safari', 'Cannot get it to work - as usual');
const page = await login({browser}, workerInfo);
const response = await page.goto('/org/org3/teams/team1/edit'); const response = await page.goto('/org/org3/teams/team1/edit');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);

View file

@ -5,13 +5,11 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, save_visual, login_user, load_logged_in_context} from './utils_e2e.ts'; import {save_visual, test} from './utils_e2e.ts';
test('Follow actions', async ({browser}, workerInfo) => { test.use({user: 'user2'});
await login_user(browser, workerInfo, 'user2');
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const page = await context.newPage();
test('Follow actions', async ({page}) => {
await page.goto('/user1'); await page.goto('/user1');
// Check if following and then unfollowing works. // Check if following and then unfollowing works.

View file

@ -4,11 +4,9 @@
// @watch end // @watch end
import {expect, type Locator} from '@playwright/test'; import {expect, type Locator} from '@playwright/test';
import {test, save_visual, login_user, load_logged_in_context} from './utils_e2e.ts'; import {save_visual, test} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.use({user: 'user2'});
await login_user(browser, workerInfo, 'user2');
});
const assertReactionCounts = (comment: Locator, counts: unknown) => const assertReactionCounts = (comment: Locator, counts: unknown) =>
expect(async () => { expect(async () => {
@ -26,6 +24,7 @@ const assertReactionCounts = (comment: Locator, counts: unknown) =>
]), ]),
), ),
); );
// eslint-disable-next-line playwright/no-standalone-expect
return expect(reactions).toStrictEqual(counts); return expect(reactions).toStrictEqual(counts);
}).toPass(); }).toPass();
@ -35,10 +34,7 @@ async function toggleReaction(menu: Locator, reaction: string) {
await menu.locator(`[role=menuitem][data-reaction-content="${reaction}"]`).click(); await menu.locator(`[role=menuitem][data-reaction-content="${reaction}"]`).click();
} }
test('Reaction Selectors', async ({browser}, workerInfo) => { test('Reaction Selectors', async ({page}) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const page = await context.newPage();
const response = await page.goto('/user2/repo1/issues/1'); const response = await page.goto('/user2/repo1/issues/1');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);

View file

@ -9,24 +9,18 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, save_visual, load_logged_in_context} from './utils_e2e.ts'; import {save_visual, test} from './utils_e2e.ts';
import {validate_form} from './shared/forms.ts'; import {validate_form} from './shared/forms.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.use({user: 'user2'});
await login_user(browser, workerInfo, 'user2');
});
test.describe.configure({ test.describe.configure({
timeout: 30000, timeout: 30000,
}); });
test('External Release Attachments', async ({browser, isMobile}, workerInfo) => { test('External Release Attachments', async ({page, isMobile}) => {
test.skip(isMobile); test.skip(isMobile);
const context = await load_logged_in_context(browser, workerInfo, 'user2');
/** @type {import('@playwright/test').Page} */
const page = await context.newPage();
// Click "New Release" // Click "New Release"
await page.goto('/user2/repo2/releases'); await page.goto('/user2/repo2/releases');
await page.click('.button.small.primary'); await page.click('.button.small.primary');

View file

@ -5,13 +5,9 @@
// @watch end // @watch end
import {expect, type Page} from '@playwright/test'; import {expect, type Page} from '@playwright/test';
import {test, save_visual, login_user, login} from './utils_e2e.ts'; import {save_visual, test} from './utils_e2e.ts';
import {accessibilityCheck} from './shared/accessibility.ts'; import {accessibilityCheck} from './shared/accessibility.ts';
test.beforeAll(async ({browser}, workerInfo) => {
await login_user(browser, workerInfo, 'user2');
});
async function assertSelectedLines(page: Page, nums: string[]) { async function assertSelectedLines(page: Page, nums: string[]) {
const pageAssertions = async () => { const pageAssertions = async () => {
expect( expect(
@ -81,8 +77,10 @@ test('Readable diff', async ({page}, workerInfo) => {
} }
}); });
test('Username highlighted in commits', async ({browser}, workerInfo) => { test.describe('As authenticated user', () => {
const page = await login({browser}, workerInfo); test.use({user: 'user2'});
test('Username highlighted in commits', async ({page}) => {
await page.goto('/user2/mentions-highlighted/commits/branch/main'); await page.goto('/user2/mentions-highlighted/commits/branch/main');
// check first commit // check first commit
await page.getByRole('link', {name: 'A commit message which'}).click(); await page.getByRole('link', {name: 'A commit message which'}).click();
@ -97,4 +95,5 @@ test('Username highlighted in commits', async ({browser}, workerInfo) => {
await expect(page.getByRole('link', {name: '@user1'})).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); await expect(page.getByRole('link', {name: '@user1'})).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)');
await accessibilityCheck({page}, ['.commit-header'], [], []); await accessibilityCheck({page}, ['.commit-header'], [], []);
await save_visual(page); await save_visual(page);
});
}); });

View file

@ -3,15 +3,13 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, save_visual, login_user, load_logged_in_context} from './utils_e2e.ts'; import {test, save_visual, test_context} from './utils_e2e.ts';
test.beforeAll(({browser}, workerInfo) => login_user(browser, workerInfo, 'user2')); test.use({user: 'user2'});
test('Migration Progress Page', async ({page: unauthedPage, browser}, workerInfo) => { test('Migration Progress Page', async ({page, browser}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Flaky actionability checks on Mobile Safari'); test.skip(workerInfo.project.name === 'Mobile Safari', 'Flaky actionability checks on Mobile Safari');
const page = await (await load_logged_in_context(browser, workerInfo, 'user2')).newPage();
expect((await page.goto('/user2/invalidrepo'))?.status(), 'repo should not exist yet').toBe(404); expect((await page.goto('/user2/invalidrepo'))?.status(), 'repo should not exist yet').toBe(404);
await page.goto('/repo/migrate?service_type=1'); await page.goto('/repo/migrate?service_type=1');
@ -23,10 +21,12 @@ test('Migration Progress Page', async ({page: unauthedPage, browser}, workerInfo
await form.locator('button.primary').click({timeout: 5000}); await form.locator('button.primary').click({timeout: 5000});
await expect(page).toHaveURL('user2/invalidrepo'); await expect(page).toHaveURL('user2/invalidrepo');
await save_visual(page); await save_visual(page);
// page screenshot of unauthedPage is checked automatically after the test // page screenshot of unauthenticatedPage is checked automatically after the test
expect((await unauthedPage.goto('/user2/invalidrepo'))?.status(), 'public migration page should be accessible').toBe(200); const ctx = await test_context(browser);
await expect(unauthedPage.locator('#repo_migrating_progress')).toBeVisible(); const unauthenticatedPage = await ctx.newPage();
expect((await unauthenticatedPage.goto('/user2/invalidrepo'))?.status(), 'public migration page should be accessible').toBe(200);
await expect(unauthenticatedPage.locator('#repo_migrating_progress')).toBeVisible();
await page.reload(); await page.reload();
await expect(page.locator('#repo_migrating_failed')).toBeVisible(); await expect(page.locator('#repo_migrating_failed')).toBeVisible();

View file

@ -4,15 +4,12 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, dynamic_id, save_visual, login_user, login} from './utils_e2e.ts'; import {test, dynamic_id, save_visual} from './utils_e2e.ts';
import {validate_form} from './shared/forms.ts'; import {validate_form} from './shared/forms.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.use({user: 'user2'});
await login_user(browser, workerInfo, 'user2');
});
test('New repo: invalid', async ({browser}, workerInfo) => { test('New repo: invalid', async ({page}) => {
const page = await login({browser}, workerInfo);
const response = await page.goto('/repo/create'); const response = await page.goto('/repo/create');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);
// check that relevant form content is hidden or available // check that relevant form content is hidden or available
@ -28,8 +25,7 @@ test('New repo: invalid', async ({browser}, workerInfo) => {
await save_visual(page); await save_visual(page);
}); });
test('New repo: initialize', async ({browser}, workerInfo) => { test('New repo: initialize', async ({page}, workerInfo) => {
const page = await login({browser}, workerInfo);
const response = await page.goto('/repo/create'); const response = await page.goto('/repo/create');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);
// check that relevant form content is hidden or available // check that relevant form content is hidden or available
@ -62,8 +58,7 @@ test('New repo: initialize', async ({browser}, workerInfo) => {
await save_visual(page); await save_visual(page);
}); });
test('New repo: initialize later', async ({browser}, workerInfo) => { test('New repo: initialize later', async ({page}) => {
const page = await login({browser}, workerInfo);
const response = await page.goto('/repo/create'); const response = await page.goto('/repo/create');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);
@ -97,9 +92,8 @@ test('New repo: initialize later', async ({browser}, workerInfo) => {
await save_visual(page); await save_visual(page);
}); });
test('New repo: from template', async ({browser}, workerInfo) => { test('New repo: from template', async ({page}, workerInfo) => {
test.skip(['Mobile Safari', 'webkit'].includes(workerInfo.project.name), 'WebKit browsers seem to have CORS issues with localhost here.'); test.skip(['Mobile Safari', 'webkit'].includes(workerInfo.project.name), 'WebKit browsers seem to have CORS issues with localhost here.');
const page = await login({browser}, workerInfo);
const response = await page.goto('/repo/create'); const response = await page.goto('/repo/create');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);
@ -114,8 +108,7 @@ test('New repo: from template', async ({browser}, workerInfo) => {
await save_visual(page); await save_visual(page);
}); });
test('New repo: label set', async ({browser}, workerInfo) => { test('New repo: label set', async ({page}) => {
const page = await login({browser}, workerInfo);
await page.goto('/repo/create'); await page.goto('/repo/create');
const reponame = dynamic_id(); const reponame = dynamic_id();

View file

@ -7,16 +7,13 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, save_visual, login_user, login} from './utils_e2e.ts'; import {test, save_visual} from './utils_e2e.ts';
import {validate_form} from './shared/forms.ts'; import {validate_form} from './shared/forms.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.use({user: 'user2'});
await login_user(browser, workerInfo, 'user2');
});
test('repo webhook settings', async ({browser}, workerInfo) => { test('repo webhook settings', async ({page}, workerInfo) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Cannot get it to work - as usual'); test.skip(workerInfo.project.name === 'Mobile Safari', 'Cannot get it to work - as usual');
const page = await login({browser}, workerInfo);
const response = await page.goto('/user2/repo1/settings/hooks/forgejo/new'); const response = await page.goto('/user2/repo1/settings/hooks/forgejo/new');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);
@ -35,9 +32,8 @@ test('repo webhook settings', async ({browser}, workerInfo) => {
}); });
test.describe('repo branch protection settings', () => { test.describe('repo branch protection settings', () => {
test('form', async ({browser}, workerInfo) => { test('form', async ({page}, {project}) => {
test.skip(workerInfo.project.name === 'Mobile Safari', 'Cannot get it to work - as usual'); test.skip(project.name === 'Mobile Safari', 'Cannot get it to work - as usual');
const page = await login({browser}, workerInfo);
const response = await page.goto('/user2/repo1/settings/branches/edit'); const response = await page.goto('/user2/repo1/settings/branches/edit');
expect(response?.status()).toBe(200); expect(response?.status()).toBe(200);
@ -56,8 +52,7 @@ test.describe('repo branch protection settings', () => {
await save_visual(page); await save_visual(page);
}); });
test.afterEach(async ({browser}, workerInfo) => { test.afterEach(async ({page}) => {
const page = await login({browser}, workerInfo);
// delete the rule for the next test // delete the rule for the next test
await page.goto('/user2/repo1/settings/branches/'); await page.goto('/user2/repo1/settings/branches/');
await page.waitForLoadState('domcontentloaded'); await page.waitForLoadState('domcontentloaded');

View file

@ -5,19 +5,12 @@
// @watch end // @watch end
import {expect} from '@playwright/test'; import {expect} from '@playwright/test';
import {test, login_user, load_logged_in_context} from './utils_e2e.ts'; import {test} from './utils_e2e.ts';
test.beforeAll(async ({browser}, workerInfo) => { test.describe('desktop viewport as user 2', () => {
await login_user(browser, workerInfo, 'user2'); test.use({user: 'user2', viewport: {width: 1920, height: 300}});
});
test.describe('desktop viewport', () => {
test.use({viewport: {width: 1920, height: 300}});
test('Settings button on right of repo header', async ({browser}, workerInfo) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const page = await context.newPage();
test('Settings button on right of repo header', async ({page}) => {
await page.goto('/user2/repo1'); await page.goto('/user2/repo1');
const settingsBtn = page.locator('.overflow-menu-items>#settings-btn'); const settingsBtn = page.locator('.overflow-menu-items>#settings-btn');
@ -27,24 +20,7 @@ test.describe('desktop viewport', () => {
await expect(page.locator('.overflow-menu-button')).toHaveCount(0); await expect(page.locator('.overflow-menu-button')).toHaveCount(0);
}); });
test('Settings button on right of repo header also when add more button is shown', async ({browser}, workerInfo) => { test('Settings button on right of org header', async ({page}) => {
await login_user(browser, workerInfo, 'user12');
const context = await load_logged_in_context(browser, workerInfo, 'user12');
const page = await context.newPage();
await page.goto('/user12/repo10');
const settingsBtn = page.locator('.overflow-menu-items>#settings-btn');
await expect(settingsBtn).toBeVisible();
await expect(settingsBtn).toHaveClass(/right/);
await expect(page.locator('.overflow-menu-button')).toHaveCount(0);
});
test('Settings button on right of org header', async ({browser}, workerInfo) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const page = await context.newPage();
await page.goto('/org3'); await page.goto('/org3');
const settingsBtn = page.locator('.overflow-menu-items>#settings-btn'); const settingsBtn = page.locator('.overflow-menu-items>#settings-btn');
@ -53,6 +29,24 @@ test.describe('desktop viewport', () => {
await expect(page.locator('.overflow-menu-button')).toHaveCount(0); await expect(page.locator('.overflow-menu-button')).toHaveCount(0);
}); });
});
test.describe('desktop viewport as user12', () => {
test.use({user: 'user12', viewport: {width: 1920, height: 300}});
test('Settings button on right of repo header also when add more button is shown', async ({page}) => {
await page.goto('/user12/repo10');
const settingsBtn = page.locator('.overflow-menu-items>#settings-btn');
await expect(settingsBtn).toBeVisible();
await expect(settingsBtn).toHaveClass(/right/);
await expect(page.locator('.overflow-menu-button')).toHaveCount(0);
});
});
test.describe('desktop viewport, unauthenticated', () => {
test.use({viewport: {width: 1920, height: 300}});
test('User overview overflow menu should not be influenced', async ({page}) => { test('User overview overflow menu should not be influenced', async ({page}) => {
await page.goto('/user2'); await page.goto('/user2');
@ -64,12 +58,9 @@ test.describe('desktop viewport', () => {
}); });
test.describe('small viewport', () => { test.describe('small viewport', () => {
test.use({viewport: {width: 800, height: 300}}); test.use({user: 'user2', viewport: {width: 800, height: 300}});
test('Settings button in overflow menu of repo header', async ({browser}, workerInfo) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const page = await context.newPage();
test('Settings button in overflow menu of repo header', async ({page}) => {
await page.goto('/user2/repo1'); await page.goto('/user2/repo1');
await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0); await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0);
@ -89,10 +80,7 @@ test.describe('small viewport', () => {
expect(Array.from(new Set(items))).toHaveLength(items.length); expect(Array.from(new Set(items))).toHaveLength(items.length);
}); });
test('Settings button in overflow menu of org header', async ({browser}, workerInfo) => { test('Settings button in overflow menu of org header', async ({page}) => {
const context = await load_logged_in_context(browser, workerInfo, 'user2');
const page = await context.newPage();
await page.goto('/org3'); await page.goto('/org3');
await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0); await expect(page.locator('.overflow-menu-items>#settings-btn')).toHaveCount(0);
@ -111,6 +99,10 @@ test.describe('small viewport', () => {
const items = shownItems.concat(overflowItems); const items = shownItems.concat(overflowItems);
expect(Array.from(new Set(items))).toHaveLength(items.length); expect(Array.from(new Set(items))).toHaveLength(items.length);
}); });
});
test.describe('small viewport, unauthenticated', () => {
test.use({viewport: {width: 800, height: 300}});
test('User overview overflow menu should not be influenced', async ({page}) => { test('User overview overflow menu should not be influenced', async ({page}) => {
await page.goto('/user2'); await page.goto('/user2');

View file

@ -1,9 +1,31 @@
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, type Page} from '@playwright/test';
export const test = baseTest.extend({ import * as path from 'node:path';
context: async ({browser}, use) => {
return use(await test_context(browser)); const AUTH_PATH = 'tests/e2e/.auth';
type AuthScope = 'logout' | 'shared' | 'webauthn';
export type TestOptions = {
forEachTest: void
user: string | null;
authScope: AuthScope;
};
export const test = baseTest.extend<TestOptions>({
context: async ({browser, user, authScope, contextOptions}, use, {project}) => {
if (user && authScope) {
const browserName = project.name.toLowerCase().replace(' ', '-');
contextOptions.storageState = path.join(AUTH_PATH, `state-${browserName}-${user}-${authScope}.json`);
} else {
// if no user is given, ensure to have clean state
contextOptions.storageState = {cookies: [], origins: []};
}
return use(await test_context(browser, contextOptions));
}, },
user: null,
authScope: 'shared',
// see https://playwright.dev/docs/test-fixtures#adding-global-beforeeachaftereach-hooks // see https://playwright.dev/docs/test-fixtures#adding-global-beforeeachaftereach-hooks
forEachTest: [async ({page}, use) => { forEachTest: [async ({page}, use) => {
await use(); await use();
@ -15,7 +37,7 @@ export const test = baseTest.extend({
}, {auto: true}], }, {auto: true}],
}); });
async function test_context(browser: Browser, options?: BrowserContextOptions) { export async function test_context(browser: Browser, options?: BrowserContextOptions) {
const context = await browser.newContext(options); const context = await browser.newContext(options);
context.on('page', (page) => { context.on('page', (page) => {

View file

@ -5,17 +5,27 @@ package e2e
import ( import (
"context" "context"
"crypto/rand"
"encoding/hex"
"fmt"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"path/filepath"
"regexp" "regexp"
"strings"
"testing" "testing"
"time" "time"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
modules_session "code.gitea.io/gitea/modules/session"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"code.forgejo.org/go-chi/session"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -25,6 +35,8 @@ func onForgejoRunTB(t testing.TB, callback func(testing.TB, *url.URL), prepare .
if len(prepare) == 0 || prepare[0] { if len(prepare) == 0 || prepare[0] {
defer tests.PrepareTestEnv(t, 1)() defer tests.PrepareTestEnv(t, 1)()
} }
createSessions(t)
s := http.Server{ s := http.Server{
Handler: testE2eWebRoutes, Handler: testE2eWebRoutes,
} }
@ -64,3 +76,118 @@ func onForgejoRun(t *testing.T, callback func(*testing.T, *url.URL), prepare ...
callback(t.(*testing.T), u) callback(t.(*testing.T), u)
}, prepare...) }, prepare...)
} }
func createSessions(t testing.TB) {
t.Helper()
// copied from playwright.config.ts
browsers := []string{
"chromium",
"firefox",
"webkit",
"Mobile Chrome",
"Mobile Safari",
}
scopes := []string{
"shared",
}
users := []string{
"user1",
"user2",
"user12",
"user40",
}
authState := filepath.Join(filepath.Dir(setting.AppPath), "tests", "e2e", ".auth")
err := os.RemoveAll(authState)
require.NoError(t, err)
err = os.MkdirAll(authState, os.ModePerm)
require.NoError(t, err)
createSessionCookie := stateHelper(t)
for _, user := range users {
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: strings.ToLower(user)})
for _, browser := range browsers {
for _, scope := range scopes {
stateFile := strings.ReplaceAll(strings.ToLower(fmt.Sprintf("state-%s-%s-%s.json", browser, user, scope)), " ", "-")
createSessionCookie(filepath.Join(authState, stateFile), u)
}
}
}
}
func stateHelper(t testing.TB) func(stateFile string, user *user_model.User) {
type Cookie struct {
Name string `json:"name"`
Value string `json:"value"`
Domain string `json:"domain"`
Path string `json:"path"`
Expires int `json:"expires"`
HTTPOnly bool `json:"httpOnly"`
Secure bool `json:"secure"`
SameSite string `json:"sameSite"`
}
type BrowserState struct {
Cookies []Cookie `json:"cookies"`
Origins []string `json:"origins"`
}
options := session.Options{
Provider: setting.SessionConfig.Provider,
ProviderConfig: setting.SessionConfig.ProviderConfig,
CookieName: setting.SessionConfig.CookieName,
CookiePath: setting.SessionConfig.CookiePath,
Gclifetime: setting.SessionConfig.Gclifetime,
Maxlifetime: setting.SessionConfig.Maxlifetime,
Secure: setting.SessionConfig.Secure,
SameSite: setting.SessionConfig.SameSite,
Domain: setting.SessionConfig.Domain,
}
opt := session.PrepareOptions([]session.Options{options})
vsp := modules_session.VirtualSessionProvider{}
err := vsp.Init(opt.Maxlifetime, opt.ProviderConfig)
require.NoError(t, err)
return func(stateFile string, user *user_model.User) {
buf := make([]byte, opt.IDLength/2)
_, err = rand.Read(buf)
require.NoError(t, err)
sessionID := hex.EncodeToString(buf)
s, err := vsp.Read(sessionID)
require.NoError(t, err)
err = s.Set("uid", user.ID)
require.NoError(t, err)
err = s.Release()
require.NoError(t, err)
state := BrowserState{
Cookies: []Cookie{
{
Name: opt.CookieName,
Value: sessionID,
Domain: setting.Domain,
Path: "/",
Expires: -1,
HTTPOnly: true,
Secure: false,
SameSite: "Lax",
},
},
Origins: []string{},
}
jsonData, err := json.Marshal(state)
require.NoError(t, err)
err = os.WriteFile(stateFile, jsonData, 0o644)
require.NoError(t, err)
}
}