sidebar-browser.e2e.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. /** Keyless Browser-tab coverage through the shipped Web composition. */
  2. import { mkdir } from 'node:fs/promises'
  3. import { join } from 'node:path'
  4. import { fileURLToPath } from 'node:url'
  5. import type { Browser, Page } from 'playwright'
  6. import { chromium } from 'playwright'
  7. import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
  8. import type {} from '@deepseek-ai/dsh-agent'
  9. import {
  10. assertFixtureInventory,
  11. compareOrRefreshGolden,
  12. launchWebScaffold,
  13. watchConsole,
  14. webSnapshotMode,
  15. type WebScaffold,
  16. } from './scaffold.ts'
  17. import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
  18. const FIXTURE = fileURLToPath(new URL('../../../snapshots/web/lifecycle-chrome/session.v3.jsonl', import.meta.url))
  19. const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/sidebar-browser', import.meta.url))
  20. const EXPECTED = join(SNAPSHOT_DIR, 'browser.expected.md')
  21. const MODE = webSnapshotMode()
  22. const PROMPT = 'Reply with the single word LIGHTHOUSE and stop.'
  23. describe.skipIf(MODE === 'record')('web e2e: Sidebar Browser', () => {
  24. let scaffold: WebScaffold
  25. let browser: Browser
  26. let page: Page
  27. let tripwire: ReturnType<typeof watchConsole>
  28. beforeAll(async () => {
  29. scaffold = await launchWebScaffold({ replayFixture: FIXTURE, compareReplaySession: false })
  30. browser = await chromium.launch()
  31. page = await newEnglishPage(browser)
  32. await page.route('https://browser.test/**', async (route) => {
  33. const name = new URL(route.request().url()).pathname.slice(1) || 'one'
  34. await route.fulfill({
  35. contentType: 'text/html',
  36. body: `<h1>${name}</h1><a href="/inside">Inside navigation</a><p>isolated HTTPS fixture</p>`,
  37. })
  38. })
  39. await page.route('http://127.0.0.1:3080/**', async route => route.fulfill({
  40. contentType: 'text/html',
  41. body: '<h1>loopback</h1>',
  42. }))
  43. tripwire = watchConsole(page)
  44. await page.goto(scaffold.authenticatedUrl, { waitUntil: 'load' })
  45. await connectFreshWorkspace(page, scaffold.workspaceCwd)
  46. })
  47. afterAll(async () => {
  48. await browser?.close()
  49. await scaffold?.close()
  50. })
  51. it('navigates sandboxed HTTP(S) with application-known history', async () => {
  52. onTestFailed(() => saveFailureShot(page, 'web-e2e-sidebar-browser'))
  53. const settled = scaffold.whenTurnSettled()
  54. const composer = page.locator('[data-composer-input]').first()
  55. await composer.fill(PROMPT)
  56. await composer.press('Enter')
  57. await settled
  58. await page.getByText('LIGHTHOUSE', { exact: true }).waitFor({ timeout: 15_000 })
  59. const column = page.locator('[data-rightbar-col]')
  60. await page.locator('[data-sidebar-right-expand]').click()
  61. await column.locator('[data-sidebar-right-guide-entry="browser"]').click()
  62. const input = column.getByRole('textbox', { name: 'Enter an HTTP(S) address' })
  63. await input.fill('https://browser.test/one')
  64. await input.press('Enter')
  65. const frame = column.locator('[data-sidebar-browser-frame]')
  66. await frame.waitFor({ state: 'visible' })
  67. await page.frameLocator('[data-sidebar-browser-frame]').getByRole('heading', { name: 'one' }).waitFor()
  68. expect(await frame.getAttribute('allow')).toBeNull()
  69. await column.getByRole('button', { name: 'Disable sandbox restrictions' }).click()
  70. await expect.poll(() => frame.getAttribute('sandbox')).toBeNull()
  71. await column.getByText('Sandbox restrictions are disabled; the page can navigate the top-level app and use downloads, modal dialogs, and input locks.', { exact: true }).waitFor()
  72. await column.getByRole('button', { name: 'Restore sandbox restrictions' }).click()
  73. await expect.poll(() => frame.getAttribute('sandbox')).toBe('allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox')
  74. await page.frameLocator('[data-sidebar-browser-frame]').getByRole('link', { name: 'Inside navigation' }).click()
  75. await page.frameLocator('[data-sidebar-browser-frame]').getByRole('heading', { name: 'inside' }).waitFor()
  76. await column.getByText('URL changed', { exact: true }).waitFor()
  77. expect(await column.getByRole('button', { name: 'Back', exact: true }).isDisabled()).toBe(true)
  78. expect(await column.getByRole('button', { name: 'Forward', exact: true }).isDisabled()).toBe(true)
  79. expect(await column.getByRole('button', { name: 'Open in system browser', exact: true }).isDisabled()).toBe(true)
  80. await column.getByRole('button', { name: 'Reload' }).click()
  81. await page.frameLocator('[data-sidebar-browser-frame]').getByRole('heading', { name: 'one' }).waitFor()
  82. await input.fill('https://browser.test/two')
  83. await input.press('Enter')
  84. await page.frameLocator('[data-sidebar-browser-frame]').getByRole('heading', { name: 'two' }).waitFor()
  85. await column.getByRole('button', { name: 'Back', exact: true }).click()
  86. await page.frameLocator('[data-sidebar-browser-frame]').getByRole('heading', { name: 'one' }).waitFor()
  87. await column.getByRole('button', { name: 'Forward', exact: true }).click()
  88. await page.frameLocator('[data-sidebar-browser-frame]').getByRole('heading', { name: 'two' }).waitFor()
  89. await input.fill('http://127.0.0.1:3080/preview')
  90. await input.press('Enter')
  91. await page.frameLocator('[data-sidebar-browser-frame]').getByRole('heading', { name: 'loopback' }).waitFor()
  92. expect(await frame.getAttribute('sandbox')).toBe('allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox')
  93. await input.fill('file:///work/index.html')
  94. await input.press('Enter')
  95. const blocked = await column.getByRole('alert').innerText()
  96. expect(blocked).toBe('Only HTTP and HTTPS addresses are supported; use Document Preview for local files.')
  97. expect(tripwire.pageErrors).toEqual([])
  98. expect(tripwire.warnings).toEqual([])
  99. await mkdir(SNAPSHOT_DIR, { recursive: true })
  100. await compareOrRefreshGolden(EXPECTED, [
  101. '# Sidebar Browser', '',
  102. '- HTTPS sandbox: allow-scripts allow-forms allow-same-origin allow-popups allow-popups-to-escape-sandbox',
  103. '- Permissions Policy: browser defaults',
  104. '- Sandbox toggle: per-tab and temporary',
  105. '- Unknown navigation: marker shown; Back, Forward, and external-open disabled',
  106. '- HTTPS history: one -> two -> one -> two',
  107. '- Loopback HTTP: loaded under the default sandbox',
  108. `- Invalid protocol: ${blocked}`,
  109. ].join('\n'), MODE)
  110. await assertFixtureInventory(SNAPSHOT_DIR, ['browser.expected.md'])
  111. })
  112. })