// @watch start // modules/actions/** // routers/web/shared/actions/** // routers/web/web.go // services/forms/runner.go // templates/admin/runners/** // templates/org/settings/runner* // templates/repo/settings/runner* // templates/shared/actions/runner* // templates/user/settings/runner* // web_src/css/actions.css // @watch end import {test} from './utils_e2e.ts'; import {expect} from '@playwright/test'; const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; const tokenPattern = /^[0-9a-f]{40}$/; test.describe('Runners of user2', () => { test.use({user: 'user2'}); test('usable runners are visible', async ({page}) => { await page.goto('/user/settings/actions/runners'); await expect(page.getByRole('heading', {name: 'Manage runners'})).toBeVisible(); const runnerContainer = page.locator('.runner-container'); const rows = runnerContainer.getByRole('row'); // We cannot assert the length of the table because it's influenced by global fixtures. It also changes depending on // the ordering of tests. await expect(rows.nth(0)).toHaveAccessibleName('Name Labels Type Status Details Edit Delete'); await expect(page.locator('tbody tr:has-text("3a20ad8d-d5d6-4b7b-ba55-841ac8264c17")')).toMatchAriaSnapshot(` - cell "runner-2 3a20ad8d-d5d6-4b7b-ba55-841ac8264c17" - cell "docker" - cell "Individual" - cell "Offline" - cell "Show details of runner-2" - cell "Edit runner-2" - cell "Delete runner-2" `); await expect(page.locator('tbody tr:has-text("1ef59b64-93b7-4ad4-ade4-21ca13db49c0")')).toMatchAriaSnapshot(` - cell "runner-4 1ef59b64-93b7-4ad4-ade4-21ca13db49c0" - cell "docker" - cell "Global" - cell "Offline" - cell "Show details of runner-4" - cell - cell `); await page.getByRole('link', {name: 'Show details of runner-2', exact: true}).click(); await expect(page).toHaveTitle(/^Runner runner-2 .*/); await page.goto('/user/settings/actions/runners'); await page.getByRole('link', {name: 'Show details of runner-4', exact: true}).click(); await expect(page).toHaveTitle(/^Runner runner-4 .*/); }); test('runner details with tasks of repositories owned by user', async ({page}) => { await page.goto('/user/settings/actions/runners'); await page.getByRole('link', {name: 'Show details of runner-4', exact: true}).click(); await expect(page).toHaveTitle(/^Runner runner-4 .*/); await expect(page.getByRole('heading', {name: 'Runner runner-4'})).toBeVisible(); await expect(page.getByLabel('Properties of runner-4')).toMatchAriaSnapshot(` - term: UUID - definition: ${uuidPattern} - term: Type - definition: Global - term: Labels - definition: docker - term: Last online time - definition: Never - term: Status - definition: Offline - term: Ephemeral - definition: "no" - term: Description - definition: A runner for everyone `); await expect(page.getByRole('heading', {name: 'Recent tasks of this user on this runner'})).toBeVisible(); const rows = page.getByRole('row'); // Only tasks from repositories owned by user2 should appear. await expect(rows).toHaveCount(2); await expect(rows.nth(0)).toHaveAccessibleName('Run Status Repository Commit Done at'); await expect(rows.nth(1)).toHaveAccessibleName('88932 Waiting 49f55ab99b -'); }); test('create new runner', async ({page}) => { await page.goto('/user/settings/actions/runners'); await expect(page.getByRole('heading', {name: 'Manage runners'})).toBeVisible(); await page.getByRole('link', {name: 'Create new runner'}).click(); await expect(page).toHaveTitle(/^Create new runner .*/); // Submit an invalid form to test validation. await page.getByRole('button', {name: 'Create'}).click(); await expect(page.getByRole('paragraph')).toHaveText('Name cannot be empty.'); // Submit a valid form to create a runner. await page.getByRole('textbox', {name: 'Name *'}).fill('runner-991301'); await page.getByRole('textbox', {name: 'Description'}).fill('Description of runner-991301'); await page.getByRole('button', {name: 'Create'}).click(); // Verify set up instructions. await expect(page).toHaveTitle(/^Set up runner runner-991301 .*/); await expect(page.getByRole('heading', {name: 'Set up runner runner-991301'})).toBeVisible(); await page.getByRole('button', {name: 'Copy runner UUID'}).click(); const runnerUUID = await page.evaluate(() => navigator.clipboard.readText()); expect(runnerUUID).toMatch(uuidPattern); await page.getByRole('button', {name: 'Copy runner token'}).click(); const runnerToken = await page.evaluate(() => navigator.clipboard.readText()); expect(runnerToken).toMatch(tokenPattern); await expect(page.getByRole('term')).toHaveText(['UUID', 'Token']); await expect(page.getByRole('definition')).toContainText([runnerUUID, runnerToken]); await expect(page.getByRole('heading', {name: 'Using the runner configuration file'})).toBeVisible(); await expect(page.getByLabel('Snippet to insert into the runner configuration')).toContainText(`uuid: ${runnerUUID}`); await expect(page.getByLabel('Snippet to insert into the runner configuration')).toContainText(`token: ${runnerToken}`); await expect(page.getByRole('heading', {name: 'Using program options'})).toBeVisible(); await expect(page.getByLabel('How to invoke forgejo-runner')).toContainText(`--uuid ${runnerUUID}`); await expect(page.getByLabel('How to invoke forgejo-runner')).toContainText(`echo -n "${runnerToken}"`); // Go back to list of runners. await page.getByRole('link', {name: 'List of runners', exact: true}).click(); await expect(page.locator(`tbody tr:has-text("${runnerUUID}")`)).toMatchAriaSnapshot(` - cell "runner-991301 ${runnerUUID}" - cell "" - cell "Individual" - cell "Offline" - cell "Show details of runner-991301" - cell "Edit runner-991301" - cell "Delete runner-991301" `); }); test('edit runner without changing its token', async ({page}) => { await page.goto('/user/settings/actions/runners'); // We have to create a new runner because changes to fixtures would affect the remainder of the tests in this file. await page.getByRole('link', {name: 'Create new runner'}).click(); await page.getByRole('textbox', {name: 'Name *'}).fill('runner-46635'); await page.getByRole('textbox', {name: 'Description'}).fill('Description of runner-46635'); await page.getByRole('button', {name: 'Create'}).click(); // Go back to list of runners. await page.getByRole('link', {name: 'Runners', exact: true}).click(); // Edit the runner that was just created. await page.getByRole('link', {name: 'Edit runner-46635'}).click(); await expect(page).toHaveTitle(/^Edit runner runner-46635 .*/); await expect(page.getByRole('heading', {name: 'Edit runner runner-46635'})).toBeVisible(); // Make the form invalid to test validation. await page.getByRole('textbox', {name: 'Name *'}).clear(); await page.getByRole('button', {name: 'Save'}).click(); await expect(page.locator('#flash-message')).toHaveText('Name cannot be empty.'); await expect(page.getByRole('textbox', {name: 'Name *'})).toBeEmpty(); await expect(page.getByRole('textbox', {name: 'Description'})).toHaveValue('Description of runner-46635'); // Submit a valid form. await page.getByRole('textbox', {name: 'Name *'}).fill('runner-46636'); await page.getByRole('textbox', {name: 'Description'}).fill('Description of runner-46636'); await page.getByRole('button', {name: 'Save'}).click(); // Verify that the runner's properties were updated properly. await expect(page).toHaveTitle(/^Runner runner-46636 .*/); await expect(page.locator('#flash-message')).toHaveText('Runner edited successfully'); await expect(page.getByRole('heading', {name: 'Runner runner-46636'})).toBeVisible(); await expect(page.getByLabel('Properties of runner-46636')).toMatchAriaSnapshot(` - term: UUID - definition: ${uuidPattern} - term: Type - definition: Individual - term: Labels - definition - term: Last online time - definition: Never - term: Status - definition: Offline - term: Ephemeral - definition: "no" - term: Description - definition: Description of runner-46636 `); }); test('regenerate runner token', async ({page}) => { await page.goto('/user/settings/actions/runners'); await page.getByRole('link', {name: 'Edit runner-2', exact: true}).click(); await expect(page).toHaveTitle(/^Edit runner runner-2 .*/); await expect(page.getByRole('heading', {name: 'Edit runner runner-2'})).toBeVisible(); await page.getByRole('checkbox', {name: 'Regenerate token'}).check(); await page.getByRole('button', {name: 'Save'}).click(); // Verify set up instructions. await expect(page).toHaveTitle(/^Set up runner runner-2 .*/); await expect(page.getByRole('heading', {name: 'Set up runner runner-2'})).toBeVisible(); await page.getByRole('button', {name: 'Copy runner UUID'}).click(); const runnerUUID = await page.evaluate(() => navigator.clipboard.readText()); expect(runnerUUID).toEqual('3a20ad8d-d5d6-4b7b-ba55-841ac8264c17'); await page.getByRole('button', {name: 'Copy runner token'}).click(); const runnerToken = await page.evaluate(() => navigator.clipboard.readText()); expect(runnerToken).not.toEqual('9730f9d2c6c731f07582788d1a1fe72a6b999a17'); expect(runnerToken).toMatch(tokenPattern); await expect(page.getByRole('term')).toHaveText(['UUID', 'Token']); await expect(page.getByRole('definition')).toContainText([runnerUUID, runnerToken]); await expect(page.getByRole('heading', {name: 'Using the runner configuration file'})).toBeVisible(); await expect(page.getByLabel('Snippet to insert into the runner configuration')).toContainText(`uuid: ${runnerUUID}`); await expect(page.getByLabel('Snippet to insert into the runner configuration')).toContainText(`token: ${runnerToken}`); await expect(page.getByRole('heading', {name: 'Using program options'})).toBeVisible(); await expect(page.getByLabel('How to invoke forgejo-runner')).toContainText(`--uuid ${runnerUUID}`); await expect(page.getByLabel('How to invoke forgejo-runner')).toContainText(`echo -n "${runnerToken}"`); }); test('delete runner', async ({page}) => { await page.goto('/user/settings/actions/runners'); // We have to create a new runner because changes to fixtures affect the remainder of the tests in this file. await page.getByRole('link', {name: 'Create new runner'}).click(); await page.getByRole('textbox', {name: 'Name *'}).fill('runner-660332'); await page.getByRole('textbox', {name: 'Description'}).fill('Description of runner-660332'); await page.getByRole('button', {name: 'Create'}).click(); // Go back to list of runners. await page.getByRole('link', {name: 'Runners', exact: true}).click(); await expect(page.getByRole('heading', {name: 'Manage runners'})).toBeVisible(); await expect(page.getByRole('document')).toContainText('runner-660332'); // Delete the runner that was just created. await page.getByRole('button', {name: 'Delete runner-660332'}).click(); // Confirm deletion await expect(page.getByRole('document')).toContainText('Confirm to delete this runner'); await page.getByRole('button', {name: 'Yes', exact: true}).click(); // Verify that the runner is gone. await expect(page.locator('#flash-message')).toHaveText('Runner deleted successfully'); await expect(page.getByRole('document')).not.toContainText('runner-660332'); }); }); test.describe('Global runners', () => { test.use({user: 'user1'}); test('all runners are visible', async ({page}) => { await page.goto('/admin/actions/runners'); await expect(page.getByRole('heading', {name: 'Manage runners'})).toBeVisible(); const runnerContainer = page.locator('.runner-container'); const rows = runnerContainer.getByRole('row'); // We cannot assert the length of the table because it's influenced by global fixtures. It also changes depending on // the ordering of tests. await expect(rows.nth(0)).toHaveAccessibleName('Name Labels Type Status Details Edit Delete'); await expect(page.locator('tbody tr:has-text("8f940b0b-32a2-479a-9d48-06ab8d8a0b90")')).toMatchAriaSnapshot(` - cell "runner-1 8f940b0b-32a2-479a-9d48-06ab8d8a0b90" - cell "debian gpu" - cell "Organization" - cell "Offline" - cell "Show details of runner-1" - cell "Edit runner-1" - cell "Delete runner-1" `); await expect(page.locator('tbody tr:has-text("3a20ad8d-d5d6-4b7b-ba55-841ac8264c17")')).toMatchAriaSnapshot(` - cell "runner-2 3a20ad8d-d5d6-4b7b-ba55-841ac8264c17" - cell "docker" - cell "Individual" - cell "Offline" - cell "Show details of runner-2" - cell "Edit runner-2" - cell "Delete runner-2" `); await expect(page.locator('tbody tr:has-text("11c9a6da-0a92-46ea-a4f1-b6c98f8c781c")')).toMatchAriaSnapshot(` - cell "runner-3 11c9a6da-0a92-46ea-a4f1-b6c98f8c781c" - cell "fedora" - cell "Organization" - cell "Offline" - cell "Show details of runner-3" - cell "Edit runner-3" - cell "Delete runner-3" `); await expect(page.locator('tbody tr:has-text("1ef59b64-93b7-4ad4-ade4-21ca13db49c0")')).toMatchAriaSnapshot(` - cell "runner-4 1ef59b64-93b7-4ad4-ade4-21ca13db49c0" - cell "docker" - cell "Global" - cell "Offline" - cell "Show details of runner-4" - cell "Edit runner-4" - cell "Delete runner-4" `); await expect(page.locator('tbody tr:has-text("69d29449-1de5-4d17-845d-e3ae11a04a1b")')).toMatchAriaSnapshot(` - cell "runner-5 69d29449-1de5-4d17-845d-e3ae11a04a1b" - cell "debian" - cell "Individual" - cell "Offline" - cell "Show details of runner-5" - cell "Edit runner-5" - cell "Delete runner-5" `); await expect(page.locator('tbody tr:has-text("9da25fbb-89a5-4520-a35a-d55fc94e4b76")')).toMatchAriaSnapshot(` - cell "runner-6 9da25fbb-89a5-4520-a35a-d55fc94e4b76" - cell "debian" - cell "Repository" - cell "Offline" - cell "Show details of runner-6" - cell "Edit runner-6" - cell "Delete runner-6" `); await expect(page.locator('tbody tr:has-text("d935307e-1d2d-4b61-8885-bc8a1c52c269")')).toMatchAriaSnapshot(` - cell "runner-7 d935307e-1d2d-4b61-8885-bc8a1c52c269" - cell "alpine" - cell "Individual" - cell "Offline" - cell "Show details of runner-7" - cell "Edit runner-7" - cell "Delete runner-7" `); }); test('runner details with all tasks visible on details page', async ({page}) => { await page.goto('/admin/actions/runners'); await page.getByRole('link', {name: 'Show details of runner-4', exact: true}).click(); await expect(page).toHaveTitle(/^Runner runner-4 .*/); await expect(page.getByRole('heading', {name: 'Runner runner-4'})).toBeVisible(); await expect(page.getByLabel('Properties of runner-4')).toMatchAriaSnapshot(` - term: UUID - definition: ${uuidPattern} - term: Type - definition: Global - term: Labels - definition: docker - term: Last online time - definition: Never - term: Status - definition: Offline - term: Ephemeral - definition: "no" - term: Description - definition: A runner for everyone `); await expect(page.getByRole('heading', {name: 'Recent tasks on this runner'})).toBeVisible(); const rows = page.getByRole('row'); // Only tasks from repositories owned by user2 should appear. await expect(rows).toHaveCount(3); await expect(rows.nth(0)).toHaveAccessibleName('Run Status Repository Commit Done at'); await expect(rows.nth(1)).toHaveAccessibleName('88932 Waiting 49f55ab99b -'); await expect(rows.nth(2)).toHaveAccessibleName('88931 Running ed38c5a46c -'); }); test('create new runner', async ({page}) => { await page.goto('/admin/actions/runners'); await expect(page.getByRole('heading', {name: 'Manage runners'})).toBeVisible(); await page.getByRole('link', {name: 'Create new runner'}).click(); await expect(page).toHaveTitle(/^Create new runner .*/); // Submit an invalid form to test validation. await page.getByRole('button', {name: 'Create'}).click(); await expect(page.getByRole('paragraph')).toHaveText('Name cannot be empty.'); // Submit a valid form to create a runner. await page.getByRole('textbox', {name: 'Name *'}).fill('runner-473465'); await page.getByRole('textbox', {name: 'Description'}).fill('Description of runner-473465'); await page.getByRole('button', {name: 'Create'}).click(); // Verify set up instructions. await expect(page).toHaveTitle(/^Set up runner runner-473465 .*/); await expect(page.getByRole('heading', {name: 'Set up runner runner-473465'})).toBeVisible(); await page.getByRole('button', {name: 'Copy runner UUID'}).click(); const runnerUUID = await page.evaluate(() => navigator.clipboard.readText()); expect(runnerUUID).toMatch(uuidPattern); await page.getByRole('button', {name: 'Copy runner token'}).click(); const runnerToken = await page.evaluate(() => navigator.clipboard.readText()); expect(runnerToken).toMatch(tokenPattern); await expect(page.getByRole('term')).toHaveText(['UUID', 'Token']); await expect(page.getByRole('definition')).toContainText([runnerUUID, runnerToken]); await expect(page.getByRole('heading', {name: 'Using the runner configuration file'})).toBeVisible(); await expect(page.getByLabel('Snippet to insert into the runner configuration')).toContainText(`uuid: ${runnerUUID}`); await expect(page.getByLabel('Snippet to insert into the runner configuration')).toContainText(`token: ${runnerToken}`); await expect(page.getByRole('heading', {name: 'Using program options'})).toBeVisible(); await expect(page.getByLabel('How to invoke forgejo-runner')).toContainText(`--uuid ${runnerUUID}`); await expect(page.getByLabel('How to invoke forgejo-runner')).toContainText(`echo -n "${runnerToken}"`); // Go back to list of runners. await page.getByRole('link', {name: 'List of runners', exact: true}).click(); await expect(page.locator(`tbody tr:has-text("${runnerUUID}")`)).toMatchAriaSnapshot(` - cell "runner-473465 ${runnerUUID}" - cell "" - cell "Global" - cell "Offline" - cell "Show details of runner-473465" - cell "Edit runner-473465" - cell "Delete runner-473465" `); }); test('edit runner without changing its token', async ({page}) => { await page.goto('/admin/actions/runners'); // We have to create a new runner because changes to fixtures would affect the remainder of the tests in this file. await page.getByRole('link', {name: 'Create new runner'}).click(); await page.getByRole('textbox', {name: 'Name *'}).fill('runner-956857'); await page.getByRole('textbox', {name: 'Description'}).fill('Description of runner-956857'); await page.getByRole('button', {name: 'Create'}).click(); // Go back to list of runners. await page.getByRole('link', {name: 'Runners', exact: true}).click(); // Edit the runner that was just created. await page.getByRole('link', {name: 'Edit runner-956857'}).click(); await expect(page).toHaveTitle(/^Edit runner runner-956857 .*/); await expect(page.getByRole('heading', {name: 'Edit runner runner-956857'})).toBeVisible(); // Make the form invalid to test validation. await page.getByRole('textbox', {name: 'Name *'}).clear(); await page.getByRole('button', {name: 'Save'}).click(); await expect(page.locator('#flash-message')).toHaveText('Name cannot be empty.'); await expect(page.getByRole('textbox', {name: 'Name *'})).toBeEmpty(); await expect(page.getByRole('textbox', {name: 'Description'})).toHaveValue('Description of runner-956857'); // Submit a valid form. await page.getByRole('textbox', {name: 'Name *'}).fill('runner-956858'); await page.getByRole('textbox', {name: 'Description'}).fill('Description of runner-956858'); await page.getByRole('button', {name: 'Save'}).click(); // Verify that the runner's properties were updated properly. await expect(page).toHaveTitle(/^Runner runner-956858 .*/); await expect(page.locator('#flash-message')).toHaveText('Runner edited successfully'); await expect(page.getByRole('heading', {name: 'Runner runner-956858'})).toBeVisible(); await expect(page.getByLabel('Properties of runner-956858')).toMatchAriaSnapshot(` - term: UUID - definition: ${uuidPattern} - term: Type - definition: Global - term: Labels - definition - term: Last online time - definition: Never - term: Status - definition: Offline - term: Ephemeral - definition: "no" - term: Description - definition: Description of runner-956858 `); }); test('delete runner', async ({page}) => { await page.goto('/admin/actions/runners'); // We have to create a new runner because changes to fixtures affect the remainder of the tests in this file. await page.getByRole('link', {name: 'Create new runner'}).click(); await page.getByRole('textbox', {name: 'Name *'}).fill('runner-650332'); await page.getByRole('textbox', {name: 'Description'}).fill('Description of runner-650332'); await page.getByRole('button', {name: 'Create'}).click(); // Go back to list of runners. await page.getByRole('link', {name: 'Runners', exact: true}).click(); await expect(page.getByRole('heading', {name: 'Manage runners'})).toBeVisible(); await expect(page.getByRole('document')).toContainText('runner-650332'); // Delete the runner that was just created. await page.getByRole('button', {name: 'Delete runner-650332'}).click(); // Confirm deletion await expect(page.getByRole('document')).toContainText('Confirm to delete this runner'); await page.getByRole('button', {name: 'Yes', exact: true}).click(); // Verify that the runner is gone. await expect(page.locator('#flash-message')).toHaveText('Runner deleted successfully'); await expect(page.getByRole('document')).not.toContainText('runner-650332'); }); }); test.describe('Organization runners', () => { test.use({user: 'user2'}); test('usable runners are visible', async ({page}) => { await page.goto('/org/org3/settings/actions/runners'); await expect(page.getByRole('heading', {name: 'Manage runners'})).toBeVisible(); const runnerContainer = page.locator('.runner-container'); const rows = runnerContainer.getByRole('row'); // We cannot assert the length of the table because it's influenced by global fixtures. It also changes depending on // the ordering of tests. await expect(rows.nth(0)).toHaveAccessibleName('Name Labels Type Status Details Edit Delete'); await expect(page.locator('tbody tr:has-text("8f940b0b-32a2-479a-9d48-06ab8d8a0b90")')).toMatchAriaSnapshot(` - cell "runner-1 8f940b0b-32a2-479a-9d48-06ab8d8a0b90" - cell "debian gpu" - cell "Organization" - cell "Offline" - cell "Show details of runner-1" - cell "Edit runner-1" - cell "Delete runner-1" `); await expect(page.locator('tbody tr:has-text("1ef59b64-93b7-4ad4-ade4-21ca13db49c0")')).toMatchAriaSnapshot(` - cell "runner-4 1ef59b64-93b7-4ad4-ade4-21ca13db49c0" - cell "docker" - cell "Global" - cell "Offline" - cell "Show details of runner-4" - cell - cell `); await expect(page.locator('tbody tr:has-text("3a20ad8d-d5d6-4b7b-ba55-841ac8264c17")')).toBeHidden(); await expect(page.locator('tbody tr:has-text("11c9a6da-0a92-46ea-a4f1-b6c98f8c781c")')).toBeHidden(); await expect(page.locator('tbody tr:has-text("69d29449-1de5-4d17-845d-e3ae11a04a1b")')).toBeHidden(); await expect(page.locator('tbody tr:has-text("9da25fbb-89a5-4520-a35a-d55fc94e4b76")')).toBeHidden(); await expect(page.locator('tbody tr:has-text("d935307e-1d2d-4b61-8885-bc8a1c52c269")')).toBeHidden(); // Verify that details of usable runners are accessible. await page.getByRole('link', {name: 'Show details of runner-1', exact: true}).click(); await expect(page).toHaveTitle(/^Runner runner-1 .*/); await page.goto('/org/org3/settings/actions/runners'); await page.getByRole('link', {name: 'Show details of runner-4', exact: true}).click(); await expect(page).toHaveTitle(/^Runner runner-4 .*/); }); test('runner details with tasks of repositories owned by organization', async ({page}) => { await page.goto('/org/org3/settings/actions/runners'); await page.getByRole('link', {name: 'Show details of runner-4', exact: true}).click(); await expect(page).toHaveTitle(/^Runner runner-4 .*/); await expect(page.getByRole('heading', {name: 'Runner runner-4'})).toBeVisible(); await expect(page.getByLabel('Properties of runner-4')).toMatchAriaSnapshot(` - term: UUID - definition: ${uuidPattern} - term: Type - definition: Global - term: Labels - definition: docker - term: Last online time - definition: Never - term: Status - definition: Offline - term: Ephemeral - definition: "no" - term: Description - definition: A runner for everyone `); await expect(page.getByRole('heading', {name: 'Recent tasks on this runner within this organization'})).toBeVisible(); const rows = page.getByRole('row'); // Only tasks from repositories owned by org3 should appear. await expect(rows).toHaveCount(2); await expect(rows.nth(0)).toHaveAccessibleName('Run Status Repository Commit Done at'); await expect(rows.nth(1)).toHaveAccessibleName('88931 Running ed38c5a46c -'); }); test('runner details with multiple pages of tasks', async ({page}) => { await page.goto('/org/org3/settings/actions/runners'); await page.getByRole('link', {name: 'Show details of runner-1', exact: true}).click(); await expect(page).toHaveTitle(/^Runner runner-1 .*/); await expect(page.getByRole('heading', {name: 'Runner runner-1'})).toBeVisible(); await expect(page.getByRole('heading', {name: 'Recent tasks on this runner within this organization'})).toBeVisible(); const rows = page.getByRole('row'); await expect(rows).toHaveCount(31); // 30 runners plus table header await expect(rows.nth(0)).toHaveAccessibleName('Run Status Repository Commit Done at'); await expect(rows.nth(1)).toHaveAccessibleName('88930 Canceled ed4df76f86 -'); await expect(rows.nth(30)).toHaveAccessibleName('88900 Success aa06c3e960 -'); await page.getByRole('link', {name: 'Next', exact: true}).click(); await expect(rows).toHaveCount(2); await expect(rows.nth(0)).toHaveAccessibleName('Run Status Repository Commit Done at'); await expect(rows.nth(1)).toHaveAccessibleName('88899 Success d553d4419a -'); // Go back to the first page and verify that nothing has changed. await page.getByRole('link', {name: 'Previous', exact: true}).click(); await expect(rows).toHaveCount(31); // 30 runners plus table header await expect(rows.nth(0)).toHaveAccessibleName('Run Status Repository Commit Done at'); await expect(rows.nth(1)).toHaveAccessibleName('88930 Canceled ed4df76f86 -'); await expect(rows.nth(30)).toHaveAccessibleName('88900 Success aa06c3e960 -'); }); }); test.describe('Repository runners', () => { test.use({user: 'user2'}); test('usable runners are visible', async ({page}) => { await page.goto('/user2/test_workflows/settings/actions/runners'); await expect(page.getByRole('heading', {name: 'Manage runners'})).toBeVisible(); const runnerContainer = page.locator('.runner-container'); const rows = runnerContainer.getByRole('row'); // We cannot assert the length of the table because it's influenced by global fixtures. It also changes depending on // the ordering of tests. await expect(rows.nth(0)).toHaveAccessibleName('Name Labels Type Status Details Edit Delete'); await expect(page.locator('tbody tr:has-text("3a20ad8d-d5d6-4b7b-ba55-841ac8264c17")')).toMatchAriaSnapshot(` - cell "runner-2 3a20ad8d-d5d6-4b7b-ba55-841ac8264c17" - cell "docker" - cell "Individual" - cell "Offline" - cell "Show details of runner-2" - cell - cell `); await expect(page.locator('tbody tr:has-text("1ef59b64-93b7-4ad4-ade4-21ca13db49c0")')).toMatchAriaSnapshot(` - cell "runner-4 1ef59b64-93b7-4ad4-ade4-21ca13db49c0" - cell "docker" - cell "Global" - cell "Offline" - cell "Show details of runner-4" - cell - cell `); await expect(page.locator('tbody tr:has-text("9da25fbb-89a5-4520-a35a-d55fc94e4b76")')).toMatchAriaSnapshot(` - cell "runner-6 9da25fbb-89a5-4520-a35a-d55fc94e4b76" - cell "debian" - cell "Repository" - cell "Offline" - cell "Show details of runner-6" - cell "Edit runner-6" - cell "Delete runner-6" `); await expect(page.locator('tbody tr:has-text("11c9a6da-0a92-46ea-a4f1-b6c98f8c781c")')).toBeHidden(); await expect(page.locator('tbody tr:has-text("69d29449-1de5-4d17-845d-e3ae11a04a1b")')).toBeHidden(); await expect(page.locator('tbody tr:has-text("d935307e-1d2d-4b61-8885-bc8a1c52c269")')).toBeHidden(); // Verify that details of usable runners are accessible. await page.getByRole('link', {name: 'Show details of runner-2', exact: true}).click(); await expect(page).toHaveTitle(/^Runner runner-2 .*/); await page.goto('/user2/test_workflows/settings/actions/runners'); await page.getByRole('link', {name: 'Show details of runner-4', exact: true}).click(); await expect(page).toHaveTitle(/^Runner runner-4 .*/); await page.goto('/user2/test_workflows/settings/actions/runners'); await page.getByRole('link', {name: 'Show details of runner-6', exact: true}).click(); await expect(page).toHaveTitle(/^Runner runner-6 .*/); }); test('runner details with tasks of repository only', async ({page}) => { await page.goto('/user2/test_workflows/settings/actions/runners'); await page.getByRole('link', {name: 'Show details of runner-4', exact: true}).click(); await expect(page).toHaveTitle(/^Runner runner-4 .*/); await expect(page.getByRole('heading', {name: 'Runner runner-4'})).toBeVisible(); await expect(page.getByLabel('Properties of runner-4')).toMatchAriaSnapshot(` - term: UUID - definition: ${uuidPattern} - term: Type - definition: Global - term: Labels - definition: docker - term: Last online time - definition: Never - term: Status - definition: Offline - term: Ephemeral - definition: "no" - term: Description - definition: A runner for everyone `); await expect(page.getByRole('heading', {name: 'Recent tasks of this repository on this runner'})).toBeVisible(); const rows = page.getByRole('row'); // Only tasks from this repository should appear. await expect(rows).toHaveCount(2); await expect(rows.nth(0)).toHaveAccessibleName('Run Status Repository Commit Done at'); await expect(rows.nth(1)).toHaveAccessibleName('There are no tasks yet.'); }); });