|
| 1 | +import assert from 'node:assert/strict'; |
| 2 | +import { test } from 'vitest'; |
| 3 | +import { createWebInteractor } from '../interactors/web.ts'; |
| 4 | +import { AppError } from '../../utils/errors.ts'; |
| 5 | +import { withWebProvider, type WebProvider } from '../../platforms/web/provider.ts'; |
| 6 | + |
| 7 | +test('web interactor delegates first-slice operations to the scoped provider', async () => { |
| 8 | + const calls: string[] = []; |
| 9 | + const interactor = createWebInteractor(); |
| 10 | + const provider = makeWebProvider({ |
| 11 | + async open(target, options) { |
| 12 | + calls.push(`open:${target}:${options?.url ?? ''}`); |
| 13 | + }, |
| 14 | + async close(target) { |
| 15 | + calls.push(`close:${target ?? ''}`); |
| 16 | + }, |
| 17 | + async snapshot(options) { |
| 18 | + calls.push(`snapshot:${options?.scope ?? ''}`); |
| 19 | + return { |
| 20 | + nodes: [{ index: 0, role: 'button', label: 'Submit' }], |
| 21 | + truncated: true, |
| 22 | + }; |
| 23 | + }, |
| 24 | + async screenshot(outPath, options) { |
| 25 | + calls.push(`screenshot:${outPath}:${options?.fullscreen === true}`); |
| 26 | + }, |
| 27 | + async click(x, y) { |
| 28 | + calls.push(`click:${x}:${y}`); |
| 29 | + }, |
| 30 | + async fill(x, y, text, options) { |
| 31 | + calls.push(`fill:${x}:${y}:${text}:${options?.delayMs ?? 0}`); |
| 32 | + }, |
| 33 | + async typeText(text, options) { |
| 34 | + calls.push(`type:${text}:${options?.delayMs ?? 0}`); |
| 35 | + }, |
| 36 | + async scroll(direction, options) { |
| 37 | + calls.push(`scroll:${direction}:${options?.pixels ?? options?.amount ?? ''}`); |
| 38 | + }, |
| 39 | + }); |
| 40 | + |
| 41 | + const snapshot = await withWebProvider(provider, async () => { |
| 42 | + await interactor.open('https://example.test'); |
| 43 | + await interactor.open('app-shell', { url: 'https://example.test/deep' }); |
| 44 | + await interactor.close('app-shell'); |
| 45 | + await interactor.tap(10, 20); |
| 46 | + await interactor.focus(11, 21); |
| 47 | + await interactor.fill(12, 22, 'hello', 5); |
| 48 | + await interactor.type('world', 6); |
| 49 | + await interactor.scroll('down', { pixels: 400 }); |
| 50 | + await interactor.screenshot('/tmp/web.png', { fullscreen: true }); |
| 51 | + return await interactor.snapshot({ scope: 'main' }); |
| 52 | + }); |
| 53 | + |
| 54 | + assert.deepEqual(calls, [ |
| 55 | + 'open:https://example.test:', |
| 56 | + 'open:https://example.test/deep:https://example.test/deep', |
| 57 | + 'close:app-shell', |
| 58 | + 'click:10:20', |
| 59 | + 'click:11:21', |
| 60 | + 'fill:12:22:hello:5', |
| 61 | + 'type:world:6', |
| 62 | + 'scroll:down:400', |
| 63 | + 'screenshot:/tmp/web.png:true', |
| 64 | + 'snapshot:main', |
| 65 | + ]); |
| 66 | + assert.equal(snapshot.backend, 'web'); |
| 67 | + assert.equal(snapshot.truncated, true); |
| 68 | + assert.deepEqual(snapshot.nodes, [{ index: 0, role: 'button', label: 'Submit' }]); |
| 69 | +}); |
| 70 | + |
| 71 | +test('web interactor reports unsupported operations explicitly', async () => { |
| 72 | + const interactor = createWebInteractor(); |
| 73 | + |
| 74 | + await assert.rejects( |
| 75 | + () => interactor.back(), |
| 76 | + (error: unknown) => |
| 77 | + error instanceof AppError && |
| 78 | + error.code === 'UNSUPPORTED_OPERATION' && |
| 79 | + error.message === 'back is not supported on web', |
| 80 | + ); |
| 81 | +}); |
| 82 | + |
| 83 | +function makeWebProvider(overrides: Partial<WebProvider> = {}): WebProvider { |
| 84 | + return { |
| 85 | + open: async () => {}, |
| 86 | + close: async () => {}, |
| 87 | + snapshot: async () => ({ nodes: [] }), |
| 88 | + screenshot: async () => {}, |
| 89 | + click: async () => {}, |
| 90 | + fill: async () => {}, |
| 91 | + typeText: async () => {}, |
| 92 | + scroll: async () => {}, |
| 93 | + ...overrides, |
| 94 | + }; |
| 95 | +} |
0 commit comments