default-product-isolation.e2e.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. /** Chromium acceptance of the shipped Web profile's actual Client plugin and module registries. */
  2. import { FiberState } from '@deepseek-ai/cordis'
  3. import type { Context, Plugin, RegistryService } from '@deepseek-ai/cordis'
  4. import type { ClientModuleLoader, ClientModuleLoaderTarget } from '@deepseek-ai/dsh-client-modules/client'
  5. import { chromium } from 'playwright'
  6. import { expect, it } from 'vitest'
  7. import { withDefaultWeb } from '../../cli/tests/profiles/web/tests/default-web-process.ts'
  8. import { newEnglishPage } from './support.ts'
  9. interface ClientObservation {
  10. ctx?: Context
  11. modules?: ClientModuleLoader
  12. }
  13. async function readClientRoster() {
  14. const observation = Reflect.get(globalThis, '__dshIsolationObservation') as ClientObservation
  15. const { ctx, modules } = observation
  16. if (ctx === undefined || modules === undefined) throw new Error('Real Client registry was not observed')
  17. await ctx.loader.await()
  18. const callbacks = new Map<object, string[]>()
  19. for (const [id, record] of modules.loadCache) {
  20. if (typeof record.exports !== 'object' || record.exports === null) continue
  21. for (const value of Object.values(record.exports)) {
  22. if (typeof value !== 'function' && (typeof value !== 'object' || value === null)) continue
  23. const callback = ctx.registry.resolve(value as Plugin)
  24. if (callback !== undefined) callbacks.set(callback, [...callbacks.get(callback) ?? [], id])
  25. }
  26. }
  27. return {
  28. entries: [...ctx.loader.entries()].map(entry => ({ name: entry.options.name, state: entry.fiber?.state })),
  29. plugins: [...ctx.registry.values()].flatMap(runtime => [...runtime.fibers].map(fiber => ({
  30. name: runtime.name ?? runtime.callback.name,
  31. owner: fiber.entry?.options.name,
  32. state: fiber.state,
  33. modules: callbacks.get(runtime.callback) ?? [],
  34. }))),
  35. modules: [...modules.loadCache].flatMap(([id, record]) => [id, ...record.edges]),
  36. }
  37. }
  38. function experimentalClientReferences(roster: Awaited<ReturnType<typeof readClientRoster>>): string[] {
  39. return [
  40. ...roster.entries.map(entry => entry.name),
  41. ...roster.plugins.flatMap(plugin => [plugin.owner ?? '', ...plugin.modules]),
  42. ...roster.modules,
  43. ].filter(name => name.startsWith('@deepseek-ai/dsh-experimental-'))
  44. }
  45. it('activates the actual default Client registry without experimental packages', async (test) => {
  46. await withDefaultWeb(test, async ({ url, request }) => {
  47. const browser = await chromium.launch({ timeout: test.task.timeout })
  48. test.onTestFinished(async () => { await browser.close() })
  49. try {
  50. const page = await newEnglishPage(browser)
  51. page.setDefaultTimeout(test.task.timeout)
  52. const errors: string[] = []
  53. page.on('pageerror', error => errors.push(error.message))
  54. await page.addInitScript(() => {
  55. const observation: ClientObservation = {}
  56. Reflect.set(globalThis, '__dshIsolationObservation', observation)
  57. Object.defineProperty(globalThis, '__ModuleLoader__', {
  58. configurable: true,
  59. set(target: ClientModuleLoaderTarget) {
  60. Object.defineProperty(globalThis, '__ModuleLoader__', { configurable: true, writable: true, value: target })
  61. const create = target.create.bind(target)
  62. target.create = function (options) {
  63. const cordis = options.staticModules['@deepseek-ai/cordis'] as { RegistryService: typeof RegistryService }
  64. const prototype = cordis.RegistryService.prototype
  65. // eslint-disable-next-line @typescript-eslint/unbound-method -- apply() preserves the runtime registry receiver.
  66. const plugin = prototype.plugin
  67. // Capture the first real root and immediately restore the registry method.
  68. prototype.plugin = function (...args: Parameters<RegistryService['plugin']>) {
  69. prototype.plugin = plugin
  70. observation.ctx = this.ctx.root
  71. return plugin.apply(this, args)
  72. }
  73. const modules = create(options)
  74. observation.modules = modules
  75. return modules
  76. }
  77. },
  78. })
  79. })
  80. const navigation = await page.goto(url)
  81. expect(navigation?.status()).toBe(200)
  82. await page.getByRole('tree', { name: 'Sessions' }).waitFor({ state: 'visible' })
  83. const roster = await page.evaluate(readClientRoster)
  84. const host = await request('roster')
  85. expect(roster.entries.map(entry => entry.name).sort()).toEqual(host.client.entries.map(entry => entry.id).sort())
  86. expect(roster.entries.every(entry => entry.state === FiberState.ACTIVE)).toBe(true)
  87. expect(roster.plugins.length).toBeGreaterThan(roster.entries.length)
  88. expect(roster.plugins.some(plugin => plugin.modules.includes('@deepseek-ai/dsh-client-ui-layout'))).toBe(true)
  89. expect(experimentalClientReferences(roster)).toEqual([])
  90. const contaminatedHost = await request('mount-experimental-entry')
  91. const experimentalName = '@deepseek-ai/dsh-experimental-client-ui-agent-team'
  92. expect(contaminatedHost.client.entries.map(entry => entry.id)).toContain(experimentalName)
  93. await page.reload()
  94. await expect.poll(async () => {
  95. const current = await page.evaluate(readClientRoster)
  96. return current.entries.some(entry => entry.name === experimentalName && entry.state === FiberState.ACTIVE)
  97. }, { timeout: test.task.timeout }).toBe(true)
  98. const contaminated = await page.evaluate(readClientRoster)
  99. expect(contaminated.plugins.some(plugin => plugin.modules.includes(experimentalName)
  100. && plugin.state === FiberState.ACTIVE)).toBe(true)
  101. expect(experimentalClientReferences(contaminated)).toContain(experimentalName)
  102. expect(errors).toEqual([])
  103. } finally {
  104. await browser.close()
  105. }
  106. })
  107. })