From 2c3a7750c7977dec17d5156d42fdb4c9be90a90d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 12:43:55 +0000 Subject: [PATCH 1/3] MAINT: Bump https://github.com/astral-sh/ruff-pre-commit Bumps [https://github.com/astral-sh/ruff-pre-commit](https://github.com/astral-sh/ruff-pre-commit) from v0.17.0 to 0.17.1. - [Release notes](https://github.com/astral-sh/ruff-pre-commit/releases) - [Commits](https://github.com/astral-sh/ruff-pre-commit/compare/v0.17.0...v0.17.1) --- updated-dependencies: - dependency-name: https://github.com/astral-sh/ruff-pre-commit dependency-version: 0.17.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 39651c7892..bdd44b9f99 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -67,7 +67,7 @@ repos: - id: detect-private-key - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.17.0 + rev: v0.17.1 hooks: - id: ruff-format - id: ruff-check From 1a4f17fb92be144e03390819187fe5f0db557b10 Mon Sep 17 00:00:00 2001 From: Roman Lutz Date: Wed, 21 Oct 2026 11:40:00 -0700 Subject: [PATCH 2/3] TST: Split Operation acceptance flows to avoid shared timeout Keep startup resolution, clearing across reloads, and restored-name registration independent without increasing timeouts or removing assertions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a2e5b91d-114e-4f67-9234-01f95a972649 --- frontend/e2e/operations-acceptance.spec.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/frontend/e2e/operations-acceptance.spec.ts b/frontend/e2e/operations-acceptance.spec.ts index 258998d19b..9a3a853085 100644 --- a/frontend/e2e/operations-acceptance.spec.ts +++ b/frontend/e2e/operations-acceptance.spec.ts @@ -87,7 +87,20 @@ for (const width of [1280, 390]) { await expect.poll(() => page.evaluate(() => JSON.parse(localStorage.getItem('pyrit.userPreferences.v1.local') ?? '{}').operation, )).toEqual({ id: operation.id }) + }) + test(`keeps clearing an operation across reloads and resolves the restored default at ${width}px @seeded`, async ({ page, request }) => { + const directory = await (await request.get('/api/operations')).json() + const existing: Operation | undefined = directory.items.find((item: Operation) => item.name === PROPOSED_NAME) + const operation = existing ?? await createOperation(request, PROPOSED_NAME) + await page.goto('/chat') + const bar = page.getByRole('region', { name: 'Default Labels' }) + await expect(bar.getByRole('combobox', { name: 'Operation', exact: true })).toHaveAccessibleDescription('Not registered') + await bar.getByRole('combobox', { name: 'Operation', exact: true }).click() + await page.getByRole('option', { name: PROPOSED_NAME, exact: true }).click() + await expect.poll(() => page.evaluate(() => + JSON.parse(localStorage.getItem('pyrit.userPreferences.v1.local') ?? '{}').operation, + )).toEqual({ id: operation.id }) await bar.getByRole('button', { name: 'Clear operation', exact: true }).click() await expect.poll(() => page.evaluate(() => JSON.parse(localStorage.getItem('pyrit.userPreferences.v1.local') ?? '{}').operation, @@ -106,7 +119,13 @@ for (const width of [1280, 390]) { await expect.poll(() => page.evaluate(() => JSON.parse(localStorage.getItem('pyrit.userPreferences.v1.local') ?? '{}').operation, )).toEqual({ id: operation.id }) + }) + test(`registers an operation name restored from preferences at ${width}px @seeded`, async ({ page }) => { + await page.goto('/chat') + await waitForAppReady(page) + const bar = page.getByRole('region', { name: 'Default Labels' }) + const dialog = page.getByRole('dialog') const newName = `Restored ${width} ${Date.now()}` await page.evaluate((name: string) => { const preferences = JSON.parse(localStorage.getItem('pyrit.userPreferences.v1.local') ?? '{}') From 47b0e975a2f33b2d58f3737edf3b96f4c770edcd Mon Sep 17 00:00:00 2001 From: Roman Lutz Date: Wed, 21 Oct 2026 12:00:52 -0700 Subject: [PATCH 3/3] TST: Isolate saved Operation preferences and preserve mobile coverage Seed a complete typed preference record before startup and set each independent case's viewport. All six scenarios passed three times without retries or increased timeouts. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a2e5b91d-114e-4f67-9234-01f95a972649 --- frontend/e2e/operations-acceptance.spec.ts | 24 ++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/frontend/e2e/operations-acceptance.spec.ts b/frontend/e2e/operations-acceptance.spec.ts index 9a3a853085..62c47eb3dc 100644 --- a/frontend/e2e/operations-acceptance.spec.ts +++ b/frontend/e2e/operations-acceptance.spec.ts @@ -1,6 +1,6 @@ import { test, expect, type APIRequestContext, type Page } from '@playwright/test' -import type { AttackSummary, Finding, Operation, ScenarioRunListItem, ScenarioRunSummary } from '../src/types' +import type { AttackSummary, Finding, Operation, ScenarioRunListItem, ScenarioRunSummary, UserPreferences } from '../src/types' import { compatibilityHeaders } from './_compatibility' import { waitForAppReady } from './_runtime' @@ -90,6 +90,7 @@ for (const width of [1280, 390]) { }) test(`keeps clearing an operation across reloads and resolves the restored default at ${width}px @seeded`, async ({ page, request }) => { + await page.setViewportSize({ width, height: 844 }) const directory = await (await request.get('/api/operations')).json() const existing: Operation | undefined = directory.items.find((item: Operation) => item.name === PROPOSED_NAME) const operation = existing ?? await createOperation(request, PROPOSED_NAME) @@ -122,16 +123,23 @@ for (const width of [1280, 390]) { }) test(`registers an operation name restored from preferences at ${width}px @seeded`, async ({ page }) => { + await page.setViewportSize({ width, height: 844 }) + const newName = `Restored ${width} ${Date.now()}` + await page.addInitScript((name: string) => { + const preferences: UserPreferences = { + targets: { objective: null, adversarial: null }, + hiddenTargetRegistryNames: [], + labels: {}, + theme: 'system', + chatMarkdown: false, + operation: { name }, + } + localStorage.setItem('pyrit.userPreferences.v1.local', JSON.stringify(preferences)) + }, newName) await page.goto('/chat') - await waitForAppReady(page) const bar = page.getByRole('region', { name: 'Default Labels' }) const dialog = page.getByRole('dialog') - const newName = `Restored ${width} ${Date.now()}` - await page.evaluate((name: string) => { - const preferences = JSON.parse(localStorage.getItem('pyrit.userPreferences.v1.local') ?? '{}') - localStorage.setItem('pyrit.userPreferences.v1.local', JSON.stringify({ ...preferences, operation: { name } })) - }, newName) - await page.reload() + await expect(bar.getByRole('combobox', { name: 'Operation', exact: true })).toHaveValue(newName) await expect(bar.getByRole('combobox', { name: 'Operation', exact: true })).toHaveAccessibleDescription('Not registered') await bar.getByRole('button', { name: 'Register', exact: true }).click() await expect(dialog.getByRole('textbox', { name: 'Name', exact: true })).toHaveValue(newName)