Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 59 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
runs-on: blacksmith-4vcpu-ubuntu-2404
outputs:
app: ${{ steps.packages.outputs.app }}
appComponents: ${{ steps.packages.outputs.appComponents }}
sessionComponents: ${{ steps.packages.outputs.sessionComponents }}
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
Expand All @@ -39,18 +41,16 @@ jobs:
with:
bun-version-file: package.json

- name: Test browser suite selection
working-directory: script/github
run: bun test --root . browser-suites.test.ts

- name: Find affected packages
id: packages
env:
TURBO_SCM_BASE: ${{ github.event_name == 'pull_request' && format('{0}^1', github.sha) || github.event.before }}
TURBO_SCM_HEAD: ${{ github.sha }}
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "app=true" >> "$GITHUB_OUTPUT"
exit 0
fi
bun x turbo@2.10.2 ls --affected --filter=@opencode-ai/app --output=json > affected.json
bun -e 'const result = await Bun.file("affected.json").json(); console.log(`app=${result.packages.count > 0}`)' >> "$GITHUB_OUTPUT"
run: bun script/github/browser-suites.ts

unit:
name: unit (${{ matrix.settings.name }})
Expand Down Expand Up @@ -176,6 +176,58 @@ jobs:
working-directory: packages/www
run: bun run check:generated

components:
name: components (${{ matrix.package }})
needs: affected
strategy:
fail-fast: false
matrix:
include:
- package: app
enabled: ${{ needs.affected.outputs.appComponents }}
- package: session-ui
enabled: ${{ needs.affected.outputs.sessionComponents }}
runs-on: blacksmith-4vcpu-ubuntu-2404
steps:
# Separate runners own separate Storybook servers; the suites never race for a port.
- name: Checkout repository
if: matrix.enabled == 'true'
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1

- name: Setup Bun
if: matrix.enabled == 'true'
uses: ./.github/actions/setup-bun

- name: Setup Node for Playwright
if: matrix.enabled == 'true'
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "24.15"

- name: Install Chromium
if: matrix.enabled == 'true'
working-directory: packages/${{ matrix.package }}
run: bunx playwright install --with-deps chromium

- name: Run component tests
if: matrix.enabled == 'true'
working-directory: packages/${{ matrix.package }}
run: bun run test:components
timeout-minutes: 15
env:
CI: true

- name: Upload component artifacts
if: always() && matrix.enabled == 'true'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: components-${{ matrix.package }}-${{ github.run_attempt }}
if-no-files-found: ignore
retention-days: 7
path: |
packages/${{ matrix.package }}/component-tests/test-results
packages/${{ matrix.package }}/component-tests/playwright-report

e2e:
name: e2e (${{ matrix.settings.name }})
needs: affected
Expand Down
2 changes: 2 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/app/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
src/assets/theme.css
e2e/test-results
e2e/playwright-report
component-tests/test-results
component-tests/playwright-report
26 changes: 26 additions & 0 deletions packages/app/component-tests/composer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { expect, story } from "../../storybook/playwright/story"

// Moved from packages/app/e2e/regression/prompt-thinking-level.spec.ts
story("shows the thinking level control while relevant", async ({ mount, page }) => {
const component = await mount("opencode-composer-flow--model-and-variant")
const composer = component.locator('[data-component="composer"]')
const input = composer.locator('[data-component="composer-editor"]')
const control = composer.getByRole("button", { name: "Choose model variant" })

await page.mouse.move(0, 0)
await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur())
await expect(control).toBeVisible()

await control.click()
const high = page.getByRole("menuitemradio", { name: "high" })
await expect(high).toBeVisible()
await page.mouse.move(0, 0)
await expect(control).toBeVisible()
await expect(high).toBeVisible()
await high.click()

await input.focus()
await expect(control).toBeVisible()
await input.blur()
await expect(control).toBeVisible()
})
48 changes: 0 additions & 48 deletions packages/app/e2e/regression/review-line-comment.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,54 +12,6 @@ test.beforeEach(async ({ page }) => {
await openReview(page)
})

test("opens the comment editor when code is clicked", async ({ page }) => {
const review = page.locator('[data-component="session-review"]')
const line = review.getByText("export const value = 'after'", { exact: true })
await expectAppVisible(line)
await line.click()

await expect(review.getByRole("textbox")).toBeVisible()
await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on line 2")
})

test("opens the comment editor when a line number is clicked", async ({ page }) => {
const review = page.locator('[data-component="session-review"]')
const lineNumber = review.locator('[data-column-number="1"]').last()
await expectAppVisible(lineNumber)
await lineNumber.click()

await expect(review.getByRole("textbox")).toBeVisible()
await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on line 1")
})

test("opens the comment editor for a line number range", async ({ page }) => {
const review = page.locator('[data-component="session-review"]')
const start = review.locator('[data-column-number="1"]').last()
const end = review.locator('[data-column-number="3"]').last()
await expectAppVisible(start)
await expectAppVisible(end)

await start.dragTo(end)

await expect(review.getByRole("textbox")).toBeVisible()
await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on lines 1-3")
})

test("shows a comment button when a diff line is hovered", async ({ page }) => {
const review = page.locator('[data-component="session-review"]')
const line = review.getByText("export const first = 1", { exact: true })
await expectAppVisible(line)

const comment = review.getByRole("button", { name: "Comment", exact: true, includeHidden: true })
await expect(comment).toHaveCount(1)
await line.dispatchEvent("pointermove", { pointerType: "mouse", bubbles: true, composed: true })
await expect(comment).toBeVisible()
await expect(comment).toHaveCSS("pointer-events", "auto")
await comment.dispatchEvent("click")
await expect(review.getByRole("textbox")).toBeVisible()
await expect(review.locator('[data-slot="line-comment-editor-label"]')).toHaveText("Commenting on line 1")
})

test("stages a submitted line comment in the prompt context", async ({ page }) => {
page.on("request", (request) => {
expect.soft(request.method(), `unexpected ${request.method()} ${new URL(request.url()).pathname}`).toBe("GET")
Expand Down
3 changes: 3 additions & 0 deletions packages/app/e2e/regression/session-queue.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,15 @@ test("editing restores the existing draft and replaces only the original queue p
await view.input.fill("my in-progress draft")
await original.click()
await expect(view.input).toHaveText("tighten the error copy")
await expect(view.input).toBeFocused()
await view.input.press("Escape")
await expect(view.input).toHaveText("my in-progress draft")

await original.click()
await expect(view.input).toHaveText("tighten the error copy")
await expect(view.input).toBeFocused()
await view.input.fill("tighten the error copy and add a retry hint")
await expect(view.input).toHaveText("tighten the error copy and add a retry hint")
await view.input.press("Enter")

await expect(view.rows.locator('[data-action="session-queue-edit"]')).toHaveText([
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,43 @@
import { expect, test } from "@playwright/test"
import { assistantMessage, setupTimeline, shell, userMessage } from "../performance/timeline-stability/fixture"
import {
assistantMessage,
setupTimeline,
shell,
textPart,
userMessage,
} from "../performance/timeline-stability/fixture"

test("space activates a focused timeline button instead of scrolling", async ({ page }) => {
const shellID = "prt_space_button_shell"
await setupTimeline(page, {
messages: [userMessage(), assistantMessage([shell(shellID, "completed", lines(5))])],
messages: [
userMessage(),
assistantMessage([
shell(shellID, "completed", lines(5)),
textPart(
"prt_space_following",
"Following content leaves room to focus the command away from the bottom. ".repeat(40),
),
]),
],
settings: { shellToolPartsExpanded: false },
reducedMotion: true,
seedHistory: true,
})
const scroller = page.locator(".scroll-view__viewport", { has: page.locator("[data-timeline-row]") })
const trigger = page.getByRole("button", { name: "Used Shell" })
await expect
.poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight))
.toBeGreaterThan(300)
await trigger.scrollIntoViewIfNeeded()
await scroller.hover()
await page.mouse.wheel(0, -100)
await expect
.poll(() => scroller.evaluate((element) => element.scrollHeight - element.clientHeight - element.scrollTop))
.toBeGreaterThan(50)
await expect(trigger).toBeInViewport()
await trigger.focus()
await expect(trigger).toBeFocused()
const before = await scroller.evaluate((element) => element.scrollTop)
await trigger.press("Space")
await expect(trigger).toHaveAttribute("aria-expanded", "true")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,6 @@ import {
userText,
} from "../performance/timeline-stability/fixture"

test("renders completed write content", async ({ page }) => {
const id = "prt_file_projection_write"
await setupTimeline(page, {
messages: [
userMessage(),
assistantMessage([
toolPart(id, "write", "completed", { path: "src/write.ts", content: "export const written = true\n" }),
]),
],
settings: { editToolPartsExpanded: true },
})

await expect(page.locator(`[data-timeline-part-id="${id}"] [data-component="write-content"]`)).toBeVisible()
})

test("renders a completed single-file patch", async ({ page }) => {
const id = "prt_file_projection_single_patch"
await setupTimeline(page, {
Expand Down
53 changes: 0 additions & 53 deletions packages/app/e2e/regression/session-timeline-file-state.spec.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,15 @@ test("transitions thinking and hidden reasoning through busy to idle", async ({
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
await expect(page.getByText("Inspecting stability", { exact: true })).toBeVisible()
await expect(page.locator(`[data-timeline-part-id="${reasoningID}"]`)).toHaveCount(0)
await expect(page.locator(`[data-timeline-part-id="${renderedPartID(reasoningID)}"]`)).toHaveCount(0)
await timeline.send(partUpdated(shell("prt_reasoning_shell", "running")), 160)
await expect(page.locator('[data-timeline-row="Thinking"]')).toBeVisible()
await timeline.send(partUpdated(shell("prt_reasoning_shell", "completed", "done")), 180)
await timeline.send(messageUpdated(completedAssistantInfo(assistant)), 100)
await timeline.send(status("idle"), 300)
await expect(page.locator('[data-timeline-row="Thinking"]')).toHaveCount(0)
await expect(page.locator(`[data-timeline-part-id="${reasoningID}"]`)).toHaveCount(0)
await expect(page.locator(`[data-timeline-part-id="${renderedPartID(reasoningID)}"]`)).toHaveCount(0)
})

test("moves busy through retry and recovery to final idle content", async ({ page }) => {
Expand Down
Loading
Loading