@theaiplatform/miniapp-sdk 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -1
- package/dist/index.d.ts +26 -0
- package/dist/rspack/index.d.ts +8 -0
- package/dist/rspack/index.js +45 -20
- package/dist/sdk.d.ts +26 -0
- package/dist/testing/rstest.d.ts +33 -45
- package/dist/testing/rstest.js +163 -80
- package/dist/ui/styles.css +504 -6
- package/dist/ui/wasm.js +9229 -509
- package/dist/ui/wasm.js.LICENSE.txt +12 -0
- package/dist/ui.js +9491 -539
- package/dist/ui.js.LICENSE.txt +12 -0
- package/dist/vscode-webview.d.ts +133 -0
- package/dist/vscode-webview.js +594 -0
- package/dist/web.d.ts +26 -0
- package/package.json +14 -10
package/README.md
CHANGED
|
@@ -25,12 +25,13 @@ Start with the [Miniapp SDK documentation](https://docs.theaiplatform.app/miniap
|
|
|
25
25
|
- `@theaiplatform/miniapp-sdk/ui/tailwind.css`
|
|
26
26
|
- `@theaiplatform/miniapp-sdk/ui-components.json`
|
|
27
27
|
- `@theaiplatform/miniapp-sdk/surface`
|
|
28
|
+
- `@theaiplatform/miniapp-sdk/vscode-webview`
|
|
28
29
|
- `@theaiplatform/miniapp-sdk/config`
|
|
29
30
|
- `@theaiplatform/miniapp-sdk/rspack`
|
|
30
31
|
- `@theaiplatform/miniapp-sdk/testing/rstest`
|
|
31
32
|
- `@theaiplatform/miniapp-sdk/config-schema.json`
|
|
32
33
|
|
|
33
|
-
## In-app E2E testing
|
|
34
|
+
## In-app E2E testing
|
|
34
35
|
|
|
35
36
|
The `/testing/rstest` entry point extends Rstest's Playwright fixtures with a
|
|
36
37
|
host-provided miniapp surface and exact TAP session provenance. Tests can assert
|
|
@@ -60,6 +61,32 @@ requests through host consent instead of browser `fetch`, and
|
|
|
60
61
|
secrets remain in the host vault and are injected only by the native request
|
|
61
62
|
authority.
|
|
62
63
|
|
|
64
|
+
## Converted VS Code webviews in 0.3.3
|
|
65
|
+
|
|
66
|
+
The `/vscode-webview` entry point mounts a converted, static VS Code webview in
|
|
67
|
+
a sandboxed iframe while keeping platform authority in the outer federated
|
|
68
|
+
surface. Declarative message bindings persist document data through
|
|
69
|
+
`sdk.storage`; the synchronous `localStorage` compatibility facade is backed by
|
|
70
|
+
`sdk.session`, so standalone and channel surfaces share sign-in only for the
|
|
71
|
+
same account, workspace, installation, and package; and exact HTTPS origins are
|
|
72
|
+
mediated through `sdk.http`. The bridge does not expose the host transport, and
|
|
73
|
+
undeclared origins fail closed.
|
|
74
|
+
|
|
75
|
+
Conversion tools should emit `tapVsCodeWebviewRuntimeSource` beside the imported
|
|
76
|
+
assets and call `mountVsCodeWebview` from the generated surface. The SDK owns
|
|
77
|
+
this compatibility boundary so conversion recipes describe mappings rather
|
|
78
|
+
than introducing a second miniapp data plane. TAP's package CSP permits only
|
|
79
|
+
same-origin external scripts and forbids `<base>`, so converters must
|
|
80
|
+
externalize executable inline scripts. The bridge resolves document resource
|
|
81
|
+
URLs against the original entry before mounting and exposes the immutable
|
|
82
|
+
`window.__TAP_VSCODE_WEBVIEW_ASSET_BASE_URL__` value for reviewed replacements
|
|
83
|
+
of extension-specific asset-root placeholders.
|
|
84
|
+
|
|
85
|
+
In Surface Test Lab runs, `sdk.http` uses the run's exact-origin policy and the
|
|
86
|
+
same bounded native transport, while stored credentials remain unavailable.
|
|
87
|
+
That origin list governs host-mediated SDK requests; it is not a browser-wide
|
|
88
|
+
network sandbox for trusted Playwright test code.
|
|
89
|
+
|
|
63
90
|
`sdk.printing` is an optional desktop-only receipt printer API. It discovers
|
|
64
91
|
bounded machine-local printer metadata and host-supported 58 mm and 80 mm paper
|
|
65
92
|
profiles, then accepts only bounded semantic version-1 receipt rows with
|
package/dist/index.d.ts
CHANGED
|
@@ -348,6 +348,7 @@ export declare type MiniAppPlatformApi = {
|
|
|
348
348
|
};
|
|
349
349
|
chat: MiniAppChatApi;
|
|
350
350
|
storage: MiniAppStorageApi;
|
|
351
|
+
session: MiniAppSessionApi;
|
|
351
352
|
presence: MiniAppPresenceApi;
|
|
352
353
|
/** Desktop host capability; feature-detect before use on portable targets. */
|
|
353
354
|
http?: MiniAppHttpApi;
|
|
@@ -507,6 +508,31 @@ export declare type MiniAppReceiptTextAlignment = 'left' | 'center' | 'right';
|
|
|
507
508
|
|
|
508
509
|
export declare type MiniAppReceiptTextWeight = 'normal' | 'bold';
|
|
509
510
|
|
|
511
|
+
/**
|
|
512
|
+
* Secure session storage backed by the operating system credential store.
|
|
513
|
+
*
|
|
514
|
+
* The host derives the signed-in TAP account, active workspace, installation,
|
|
515
|
+
* and package from the authenticated frame. Channel, surface, document, and
|
|
516
|
+
* release are deliberately excluded, so every surface of one installed
|
|
517
|
+
* miniapp shares the same session only within a workspace and package updates
|
|
518
|
+
* retain it. Unlike host-managed HTTP credentials, values returned here enter
|
|
519
|
+
* miniapp JavaScript.
|
|
520
|
+
*/
|
|
521
|
+
export declare type MiniAppSessionApi = {
|
|
522
|
+
get(): MiniAppMaybePromise<MiniAppSessionEntry>;
|
|
523
|
+
set(value: MiniAppSessionValue): MiniAppMaybePromise<void>;
|
|
524
|
+
clear(): MiniAppMaybePromise<void>;
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
export declare type MiniAppSessionEntry = {
|
|
528
|
+
/** Null means that no session value currently exists for this installation. */
|
|
529
|
+
value: MiniAppSessionValue | null;
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
export declare type MiniAppSessionValue = {
|
|
533
|
+
[key: string]: MiniAppJsonValue;
|
|
534
|
+
};
|
|
535
|
+
|
|
510
536
|
export declare type MiniAppSpecialistApi = {
|
|
511
537
|
joinToChannel(channelId: string, specialistId: string): MiniAppMaybePromise<string>;
|
|
512
538
|
listWorkspace(workspaceId: string): MiniAppMaybePromise<MiniAppSpecialistSummary[]>;
|
package/dist/rspack/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Format } from 'ajv';
|
|
1
2
|
import type { LibConfig } from '@rslib/core';
|
|
2
3
|
import { ModuleFederationOptions } from '@module-federation/rsbuild-plugin';
|
|
3
4
|
import { RsbuildPlugin } from '@rsbuild/core';
|
|
@@ -29,8 +30,15 @@ declare type Config = {
|
|
|
29
30
|
|
|
30
31
|
export declare const pluginTap: (options?: Config) => RsbuildPlugin;
|
|
31
32
|
|
|
33
|
+
/** Registers the canonical JSON Schema formats used by TAP manifests. */
|
|
34
|
+
export declare const registerTapManifestAjvFormats: <Registry extends TapManifestAjvFormatRegistry>(ajv: Registry) => Registry;
|
|
35
|
+
|
|
32
36
|
export declare const tapLib: (options?: Config) => LibConfig;
|
|
33
37
|
|
|
38
|
+
declare interface TapManifestAjvFormatRegistry {
|
|
39
|
+
addFormat(name: string, format: Format): unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
34
42
|
/** Emitted-package scan used to reject machine-local build artifacts. */
|
|
35
43
|
export declare type TapPackageArtifactPortabilityOptions = {
|
|
36
44
|
output: string;
|
package/dist/rspack/index.js
CHANGED
|
@@ -320,7 +320,7 @@ const TAP_FEDERATED_SURFACE_HTML = `<!doctype html>
|
|
|
320
320
|
<head>
|
|
321
321
|
<meta charset="utf-8">
|
|
322
322
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
323
|
-
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'self'; worker-src 'self' blob:; object-src 'none'; base-uri 'none'; form-action 'none'">
|
|
323
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-src 'self'; worker-src 'self' blob:; object-src 'none'; base-uri 'none'; form-action 'none'">
|
|
324
324
|
<title>TAP mini app</title>
|
|
325
325
|
<style>html,body,#tap-root{height:100%;width:100%;margin:0}body{overflow:hidden;background:transparent}#tap-error{box-sizing:border-box;display:none;min-height:100%;padding:24px;font:14px/1.5 system-ui,sans-serif;color:#b42318;background:#fff}</style>
|
|
326
326
|
</head>
|
|
@@ -452,6 +452,7 @@ const MAX_CHECKPOINT_JSON_BYTES = 32 * 1024;
|
|
|
452
452
|
const MAX_STORAGE_NAMESPACE_CHARS = 128;
|
|
453
453
|
const MAX_STORAGE_KEY_CHARS = 512;
|
|
454
454
|
const MAX_STORAGE_JSON_BYTES = 5 * 1024 * 1024;
|
|
455
|
+
const MAX_SESSION_JSON_BYTES = 1_800;
|
|
455
456
|
const MAX_PRESENCE_ROOM_CHARS = 256;
|
|
456
457
|
const MAX_PRESENCE_JSON_BYTES = 16 * 1024;
|
|
457
458
|
const MAX_HTTP_REQUEST_JSON_BYTES = 11 * 1024 * 1024;
|
|
@@ -736,6 +737,26 @@ const storage = Object.freeze({
|
|
|
736
737
|
return invokeSdkHostAction('tap.platform.storage.delete', options, 'app.storage.delete');
|
|
737
738
|
},
|
|
738
739
|
});
|
|
740
|
+
const session = Object.freeze({
|
|
741
|
+
get() {
|
|
742
|
+
return invokeSdkHostAction('tap.platform.session.get', {}, 'app.session.get');
|
|
743
|
+
},
|
|
744
|
+
set(value) {
|
|
745
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
746
|
+
throw new Error('app.session.set requires a JSON object.');
|
|
747
|
+
}
|
|
748
|
+
checkedPlatformJson(value, MAX_SESSION_JSON_BYTES, 'app.session.set value');
|
|
749
|
+
return invokeSdkHostAction(
|
|
750
|
+
'tap.platform.session.set',
|
|
751
|
+
{ value },
|
|
752
|
+
'app.session.set',
|
|
753
|
+
12000,
|
|
754
|
+
);
|
|
755
|
+
},
|
|
756
|
+
clear() {
|
|
757
|
+
return invokeSdkHostAction('tap.platform.session.clear', {}, 'app.session.clear');
|
|
758
|
+
},
|
|
759
|
+
});
|
|
739
760
|
const checkedPresenceAddress = (options, label) => {
|
|
740
761
|
requireOptionsObject(options, label);
|
|
741
762
|
if (!validPlatformPartition(options.namespace, MAX_STORAGE_NAMESPACE_CHARS)) {
|
|
@@ -1692,6 +1713,7 @@ const miniappSdk = Object.freeze({
|
|
|
1692
1713
|
navigation,
|
|
1693
1714
|
chat: miniappChat,
|
|
1694
1715
|
storage,
|
|
1716
|
+
session,
|
|
1695
1717
|
presence,
|
|
1696
1718
|
http,
|
|
1697
1719
|
credentials,
|
|
@@ -1994,27 +2016,30 @@ const isUrl = (value)=>{
|
|
|
1994
2016
|
return false;
|
|
1995
2017
|
}
|
|
1996
2018
|
};
|
|
2019
|
+
const registerTapManifestAjvFormats = (ajv)=>{
|
|
2020
|
+
ajv.addFormat('uri', {
|
|
2021
|
+
type: 'string',
|
|
2022
|
+
validate: isUrl
|
|
2023
|
+
});
|
|
2024
|
+
ajv.addFormat('uint8', {
|
|
2025
|
+
type: 'number',
|
|
2026
|
+
validate: (value)=>Number.isInteger(value) && value >= 0 && value <= 255
|
|
2027
|
+
});
|
|
2028
|
+
ajv.addFormat('uint16', {
|
|
2029
|
+
type: 'number',
|
|
2030
|
+
validate: (value)=>Number.isInteger(value) && value >= 0 && value <= 65535
|
|
2031
|
+
});
|
|
2032
|
+
ajv.addFormat('uint64', {
|
|
2033
|
+
type: 'number',
|
|
2034
|
+
validate: (value)=>Number.isSafeInteger(value) && value >= 0
|
|
2035
|
+
});
|
|
2036
|
+
return ajv;
|
|
2037
|
+
};
|
|
1997
2038
|
const getSchemaValidator = async ()=>{
|
|
1998
2039
|
if (!schemaValidator) {
|
|
1999
|
-
const ajv = new __rspack_external_ajv_dist_2020_js_e85899db["default"]({
|
|
2040
|
+
const ajv = registerTapManifestAjvFormats(new __rspack_external_ajv_dist_2020_js_e85899db["default"]({
|
|
2000
2041
|
allErrors: true
|
|
2001
|
-
});
|
|
2002
|
-
ajv.addFormat('uri', {
|
|
2003
|
-
type: 'string',
|
|
2004
|
-
validate: isUrl
|
|
2005
|
-
});
|
|
2006
|
-
ajv.addFormat('uint8', {
|
|
2007
|
-
type: 'number',
|
|
2008
|
-
validate: (value)=>Number.isInteger(value) && value >= 0 && value <= 255
|
|
2009
|
-
});
|
|
2010
|
-
ajv.addFormat('uint16', {
|
|
2011
|
-
type: 'number',
|
|
2012
|
-
validate: (value)=>Number.isInteger(value) && value >= 0 && value <= 65535
|
|
2013
|
-
});
|
|
2014
|
-
ajv.addFormat('uint64', {
|
|
2015
|
-
type: 'number',
|
|
2016
|
-
validate: (value)=>Number.isSafeInteger(value) && value >= 0
|
|
2017
|
-
});
|
|
2042
|
+
}));
|
|
2018
2043
|
schemaValidator = ajv.compile(config_schema_namespaceObject);
|
|
2019
2044
|
}
|
|
2020
2045
|
return schemaValidator;
|
|
@@ -3512,4 +3537,4 @@ const tapLib = (options = {})=>{
|
|
|
3512
3537
|
]
|
|
3513
3538
|
};
|
|
3514
3539
|
};
|
|
3515
|
-
export { assembleTapPackage, assertPortableTapPackageArtifacts, pluginTap, tapLib };
|
|
3540
|
+
export { assembleTapPackage, assertPortableTapPackageArtifacts, pluginTap, registerTapManifestAjvFormats, tapLib };
|
package/dist/sdk.d.ts
CHANGED
|
@@ -319,6 +319,7 @@ export declare type MiniAppPlatformApi = {
|
|
|
319
319
|
};
|
|
320
320
|
chat: MiniAppChatApi;
|
|
321
321
|
storage: MiniAppStorageApi;
|
|
322
|
+
session: MiniAppSessionApi;
|
|
322
323
|
presence: MiniAppPresenceApi;
|
|
323
324
|
/** Desktop host capability; feature-detect before use on portable targets. */
|
|
324
325
|
http?: MiniAppHttpApi;
|
|
@@ -478,6 +479,31 @@ export declare type MiniAppReceiptTextAlignment = 'left' | 'center' | 'right';
|
|
|
478
479
|
|
|
479
480
|
export declare type MiniAppReceiptTextWeight = 'normal' | 'bold';
|
|
480
481
|
|
|
482
|
+
/**
|
|
483
|
+
* Secure session storage backed by the operating system credential store.
|
|
484
|
+
*
|
|
485
|
+
* The host derives the signed-in TAP account, active workspace, installation,
|
|
486
|
+
* and package from the authenticated frame. Channel, surface, document, and
|
|
487
|
+
* release are deliberately excluded, so every surface of one installed
|
|
488
|
+
* miniapp shares the same session only within a workspace and package updates
|
|
489
|
+
* retain it. Unlike host-managed HTTP credentials, values returned here enter
|
|
490
|
+
* miniapp JavaScript.
|
|
491
|
+
*/
|
|
492
|
+
export declare type MiniAppSessionApi = {
|
|
493
|
+
get(): MiniAppMaybePromise<MiniAppSessionEntry>;
|
|
494
|
+
set(value: MiniAppSessionValue): MiniAppMaybePromise<void>;
|
|
495
|
+
clear(): MiniAppMaybePromise<void>;
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
export declare type MiniAppSessionEntry = {
|
|
499
|
+
/** Null means that no session value currently exists for this installation. */
|
|
500
|
+
value: MiniAppSessionValue | null;
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
export declare type MiniAppSessionValue = {
|
|
504
|
+
[key: string]: MiniAppJsonValue;
|
|
505
|
+
};
|
|
506
|
+
|
|
481
507
|
export declare type MiniAppSpecialistApi = {
|
|
482
508
|
joinToChannel(channelId: string, specialistId: string): MiniAppMaybePromise<string>;
|
|
483
509
|
listWorkspace(workspaceId: string): MiniAppMaybePromise<MiniAppSpecialistSummary[]>;
|
package/dist/testing/rstest.d.ts
CHANGED
|
@@ -1,31 +1,33 @@
|
|
|
1
1
|
import { afterAll } from '@rstest/playwright';
|
|
2
|
-
import { afterEach } from '@rstest/playwright';
|
|
3
|
-
import { APIRequestContext } from 'playwright';
|
|
4
2
|
import { beforeAll } from '@rstest/playwright';
|
|
5
|
-
import { beforeEach } from '@rstest/playwright';
|
|
6
3
|
import type { Browser } from 'playwright';
|
|
7
4
|
import type { BrowserContext } from 'playwright';
|
|
8
5
|
import { describe } from '@rstest/playwright';
|
|
9
6
|
import { expect } from '@rstest/playwright';
|
|
10
7
|
import type { FrameLocator } from 'playwright';
|
|
11
8
|
import type { Page } from 'playwright';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
9
|
+
import type { PlaywrightFixtures } from '@rstest/playwright';
|
|
10
|
+
import type { PlaywrightTest } from '@rstest/playwright';
|
|
11
|
+
import type { TestContext } from '@rstest/core';
|
|
12
|
+
import type { TestOptions } from '@rstest/core';
|
|
15
13
|
import { z } from 'zod';
|
|
16
14
|
|
|
17
15
|
export { afterAll }
|
|
18
16
|
|
|
19
|
-
export
|
|
17
|
+
export declare const afterEach: TapAfterEachHook;
|
|
20
18
|
|
|
21
19
|
export { beforeAll }
|
|
22
20
|
|
|
23
|
-
export
|
|
21
|
+
export declare const beforeEach: TapBeforeEachHook;
|
|
24
22
|
|
|
25
23
|
export { describe }
|
|
26
24
|
|
|
27
25
|
export { expect }
|
|
28
26
|
|
|
27
|
+
declare type MergeContext<ExtraContext, FixturesContext> = {
|
|
28
|
+
[Key in keyof FixturesContext | keyof ExtraContext]: Key extends keyof FixturesContext ? FixturesContext[Key] : Key extends keyof ExtraContext ? ExtraContext[Key] : never;
|
|
29
|
+
};
|
|
30
|
+
|
|
29
31
|
/** Validate a driver session before any endpoint or provenance field is used. */
|
|
30
32
|
export declare function parseTapMiniappTestSession(value: unknown): TapMiniappTestSession;
|
|
31
33
|
|
|
@@ -60,6 +62,16 @@ declare const sessionSchema: z.ZodObject<{
|
|
|
60
62
|
testBundleDigest: z.ZodString;
|
|
61
63
|
}, z.core.$strip>;
|
|
62
64
|
|
|
65
|
+
declare type TapAfterEachCallback<ExtraContext> = (context: TapHookContext<ExtraContext>) => void | Promise<void>;
|
|
66
|
+
|
|
67
|
+
declare type TapAfterEachHook<ExtraContext = TapRstestFixtures> = (callback: TapAfterEachCallback<ExtraContext>, timeout?: number) => void;
|
|
68
|
+
|
|
69
|
+
declare type TapBeforeEachCallback<ExtraContext> = (context: TapHookContext<ExtraContext>) => void | TapAfterEachCallback<ExtraContext> | Promise<void | TapAfterEachCallback<ExtraContext>>;
|
|
70
|
+
|
|
71
|
+
declare type TapBeforeEachHook<ExtraContext = TapRstestFixtures> = (callback: TapBeforeEachCallback<ExtraContext>, timeout?: number) => void;
|
|
72
|
+
|
|
73
|
+
declare type TapHookContext<ExtraContext> = TestContext & ExtraContext;
|
|
74
|
+
|
|
63
75
|
export declare type TapMiniappTestControl = {
|
|
64
76
|
/** Restore the run's declared fixture and permission scenario. */
|
|
65
77
|
reset: () => Promise<void>;
|
|
@@ -68,6 +80,7 @@ export declare type TapMiniappTestControl = {
|
|
|
68
80
|
};
|
|
69
81
|
|
|
70
82
|
export declare type TapMiniappTestFixture = Readonly<Pick<TapMiniappTestSession, 'channelId' | 'dataScope' | 'installationId' | 'localGeneration' | 'mode' | 'packageId' | 'permissionScenario' | 'releaseDigest' | 'runId' | 'sourceDigest' | 'surfaceId' | 'surfaceAssetOrigin' | 'testBundleDigest' | 'workspaceId'> & {
|
|
83
|
+
/** Origins authorized for host-mediated SDK HTTP, not browser-wide egress. */
|
|
71
84
|
readonly allowedNetworkOrigins: readonly string[];
|
|
72
85
|
readonly credentialAliases: readonly string[];
|
|
73
86
|
control: TapMiniappTestControl;
|
|
@@ -85,47 +98,22 @@ export declare type TapRstestFixtures = {
|
|
|
85
98
|
tap: TapMiniappTestFixture;
|
|
86
99
|
};
|
|
87
100
|
|
|
101
|
+
declare type TapRstestTest<ExtraContext = TapRstestFixtures> = Omit<PlaywrightTest<ExtraContext>, 'afterEach' | 'beforeEach' | 'extend'> & {
|
|
102
|
+
(description: string, callback?: (context: TapHookContext<ExtraContext>) => void | Promise<void>, timeout?: number): void;
|
|
103
|
+
(description: string, options: TestOptions, callback?: (context: TapHookContext<ExtraContext>) => void | Promise<void>): void;
|
|
104
|
+
afterEach: TapAfterEachHook<ExtraContext>;
|
|
105
|
+
beforeEach: TapBeforeEachHook<ExtraContext>;
|
|
106
|
+
extend: <FixturesContext extends Record<string, unknown> = Record<never, never>>(fixtures: PlaywrightFixtures<FixturesContext, ExtraContext>) => TapRstestTest<MergeContext<ExtraContext, FixturesContext>>;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/** Rstest's per-test context plus TAP's authorized browser and run fixtures. */
|
|
110
|
+
export declare type TapTestContext = TestContext & TapRstestFixtures;
|
|
111
|
+
|
|
88
112
|
/**
|
|
89
113
|
* Rstest's Playwright test API with TAP-owned browser, page, iframe, and run
|
|
90
114
|
* fixtures. The upstream runner still owns scheduling, assertions, hooks, and
|
|
91
115
|
* reporting; TAP owns the privileged browser/session boundary.
|
|
92
116
|
*/
|
|
93
|
-
export declare const test:
|
|
94
|
-
browser: Browser;
|
|
95
|
-
context: BrowserContext;
|
|
96
|
-
request: APIRequestContext;
|
|
97
|
-
surface: FrameLocator;
|
|
98
|
-
page: Page;
|
|
99
|
-
tap: Readonly<Pick<{
|
|
100
|
-
schemaVersion: 1;
|
|
101
|
-
runId: string;
|
|
102
|
-
workspaceId: string;
|
|
103
|
-
channelId: string;
|
|
104
|
-
packageId: string;
|
|
105
|
-
installationId: string;
|
|
106
|
-
mode: "surface" | "live-tap";
|
|
107
|
-
dataScope: "fixture" | "selected-channel";
|
|
108
|
-
permissionScenario: string;
|
|
109
|
-
surfaceId: string;
|
|
110
|
-
surfaceSelector: string;
|
|
111
|
-
surfaceAssetOrigin: string;
|
|
112
|
-
pageUrlPrefix: string;
|
|
113
|
-
cdpEndpoint: string;
|
|
114
|
-
cdpAuthorization: string;
|
|
115
|
-
artifactDirectory: string;
|
|
116
|
-
allowedNetworkOrigins: string[];
|
|
117
|
-
credentialAliases: string[];
|
|
118
|
-
sourceDigest: string;
|
|
119
|
-
testBundleDigest: string;
|
|
120
|
-
releaseDigest?: string | undefined;
|
|
121
|
-
localGeneration?: number | undefined;
|
|
122
|
-
}, "packageId" | "installationId" | "workspaceId" | "channelId" | "mode" | "runId" | "dataScope" | "permissionScenario" | "surfaceId" | "surfaceAssetOrigin" | "releaseDigest" | "localGeneration" | "sourceDigest" | "testBundleDigest"> & {
|
|
123
|
-
readonly allowedNetworkOrigins: readonly string[];
|
|
124
|
-
readonly credentialAliases: readonly string[];
|
|
125
|
-
control: TapMiniappTestControl;
|
|
126
|
-
}>;
|
|
127
|
-
playwright: PlaywrightOptions;
|
|
128
|
-
serve: PlaywrightServe;
|
|
129
|
-
}>;
|
|
117
|
+
export declare const test: TapRstestTest;
|
|
130
118
|
|
|
131
119
|
export { }
|
package/dist/testing/rstest.js
CHANGED
|
@@ -114,6 +114,97 @@ function fixtureMetadata(session, page) {
|
|
|
114
114
|
function safeArtifactSegment(value) {
|
|
115
115
|
return value.replaceAll(/[^a-zA-Z0-9._-]/g, '-').slice(0, 160);
|
|
116
116
|
}
|
|
117
|
+
const HTTP_PROTOCOLS = new Set([
|
|
118
|
+
'http:',
|
|
119
|
+
'https:'
|
|
120
|
+
]);
|
|
121
|
+
const SAFE_INTERNAL_PROTOCOLS = new Set([
|
|
122
|
+
'about:',
|
|
123
|
+
'blob:',
|
|
124
|
+
'data:'
|
|
125
|
+
]);
|
|
126
|
+
function explicitHttpOrigin(value, label) {
|
|
127
|
+
const url = new URL(value);
|
|
128
|
+
if (!HTTP_PROTOCOLS.has(url.protocol)) throw new Error(`${label} must use HTTP or HTTPS.`);
|
|
129
|
+
return url.origin;
|
|
130
|
+
}
|
|
131
|
+
function allowedHttpOrigins(session) {
|
|
132
|
+
return new Set([
|
|
133
|
+
explicitHttpOrigin(session.pageUrlPrefix, 'TAP host page URL prefix'),
|
|
134
|
+
explicitHttpOrigin(session.surfaceAssetOrigin, 'Surface asset origin'),
|
|
135
|
+
...session.allowedNetworkOrigins.map((value)=>explicitHttpOrigin(value, 'Allowed network origin'))
|
|
136
|
+
]);
|
|
137
|
+
}
|
|
138
|
+
function classifyRequestUrl(value, allowedOrigins) {
|
|
139
|
+
let url;
|
|
140
|
+
try {
|
|
141
|
+
url = new URL(value);
|
|
142
|
+
} catch {
|
|
143
|
+
return {
|
|
144
|
+
action: 'abort',
|
|
145
|
+
reason: 'malformed-url'
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (SAFE_INTERNAL_PROTOCOLS.has(url.protocol)) return {
|
|
149
|
+
action: 'continue',
|
|
150
|
+
reason: 'safe-internal'
|
|
151
|
+
};
|
|
152
|
+
if ('file:' === url.protocol) return {
|
|
153
|
+
action: 'abort',
|
|
154
|
+
reason: 'file-scheme',
|
|
155
|
+
target: 'file:'
|
|
156
|
+
};
|
|
157
|
+
if (!HTTP_PROTOCOLS.has(url.protocol)) return {
|
|
158
|
+
action: 'abort',
|
|
159
|
+
reason: 'unsupported-scheme',
|
|
160
|
+
target: url.protocol.slice(0, 32)
|
|
161
|
+
};
|
|
162
|
+
if (!allowedOrigins.has(url.origin)) return {
|
|
163
|
+
action: 'abort',
|
|
164
|
+
reason: 'origin-not-allowed',
|
|
165
|
+
target: url.origin.slice(0, 256)
|
|
166
|
+
};
|
|
167
|
+
return {
|
|
168
|
+
action: 'continue',
|
|
169
|
+
reason: 'explicit-http-origin'
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function classifyWebSocketUrl(value, allowedOrigins) {
|
|
173
|
+
let url;
|
|
174
|
+
try {
|
|
175
|
+
url = new URL(value);
|
|
176
|
+
} catch {
|
|
177
|
+
return {
|
|
178
|
+
action: 'abort',
|
|
179
|
+
reason: 'malformed-url'
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
if ('ws:' !== url.protocol && 'wss:' !== url.protocol) return {
|
|
183
|
+
action: 'abort',
|
|
184
|
+
reason: 'file:' === url.protocol ? 'file-scheme' : 'unsupported-scheme',
|
|
185
|
+
target: url.protocol.slice(0, 32)
|
|
186
|
+
};
|
|
187
|
+
const diagnosticOrigin = url.origin.slice(0, 256);
|
|
188
|
+
url.protocol = 'ws:' === url.protocol ? 'http:' : 'https:';
|
|
189
|
+
if (!allowedOrigins.has(url.origin)) return {
|
|
190
|
+
action: 'abort',
|
|
191
|
+
reason: 'origin-not-allowed',
|
|
192
|
+
target: diagnosticOrigin
|
|
193
|
+
};
|
|
194
|
+
return {
|
|
195
|
+
action: 'continue',
|
|
196
|
+
reason: 'explicit-http-origin'
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function reportUnexpectedWebSocket(webSocket, allowedOrigins) {
|
|
200
|
+
const decision = classifyWebSocketUrl(webSocket.url(), allowedOrigins);
|
|
201
|
+
if ('continue' === decision.action) return;
|
|
202
|
+
const target = decision.target ? ` (${decision.target})` : '';
|
|
203
|
+
process.emitWarning(`TAP Test Lab observed a WebSocket outside the run's SDK HTTP origin set: ${decision.reason}${target}. This check is diagnostic only; TAP does not yet provide native browser-wide egress enforcement.`, {
|
|
204
|
+
code: 'TAP_MINIAPP_TEST_NETWORK_DIAGNOSTIC',
|
|
205
|
+
type: 'Warning'
|
|
206
|
+
});
|
|
207
|
+
}
|
|
117
208
|
const test = __rspack_external__rstest_playwright_5734ff29.test.extend({
|
|
118
209
|
browser: async (_context, use)=>{
|
|
119
210
|
const session = await loadSession();
|
|
@@ -132,70 +223,80 @@ const test = __rspack_external__rstest_playwright_5734ff29.test.extend({
|
|
|
132
223
|
const context = contexts[0];
|
|
133
224
|
if (!context) throw new Error('TAP Browser Driver context selection failed');
|
|
134
225
|
const hostPage = selectPage(context, session);
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
...session.allowedNetworkOrigins.map((value)=>new URL(value).origin)
|
|
140
|
-
]);
|
|
141
|
-
const enforceNetworkPolicy = async (route)=>{
|
|
142
|
-
const url = route.request().url();
|
|
143
|
-
const origin = originOf(url);
|
|
144
|
-
if (void 0 === origin || allowedOrigins.has(origin)) return void await route.continue();
|
|
226
|
+
const allowedOrigins = allowedHttpOrigins(session);
|
|
227
|
+
const enforceDiagnosticNetworkPolicy = async (route)=>{
|
|
228
|
+
const decision = classifyRequestUrl(route.request().url(), allowedOrigins);
|
|
229
|
+
if ('continue' === decision.action) return void await route.continue();
|
|
145
230
|
await route.abort('blockedbyclient');
|
|
146
231
|
};
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
recursive: true
|
|
151
|
-
});
|
|
152
|
-
await context.tracing.start({
|
|
153
|
-
screenshots: true,
|
|
154
|
-
snapshots: true,
|
|
155
|
-
sources: true
|
|
156
|
-
});
|
|
232
|
+
const diagnoseWebSocket = (webSocket)=>reportUnexpectedWebSocket(webSocket, allowedOrigins);
|
|
233
|
+
await context.route('**/*', enforceDiagnosticNetworkPolicy);
|
|
234
|
+
hostPage.on('websocket', diagnoseWebSocket);
|
|
157
235
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
path: (0, __rspack_external_node_path_806ed179.join)(traceDirectory, 'trace.zip')
|
|
236
|
+
const traceDirectory = (0, __rspack_external_node_path_806ed179.join)(session.artifactDirectory, 'traces', safeArtifactSegment(task.id));
|
|
237
|
+
await (0, __rspack_external_node_fs_promises_3b710708.mkdir)(traceDirectory, {
|
|
238
|
+
recursive: true
|
|
162
239
|
});
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
'',
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
240
|
+
await context.tracing.start({
|
|
241
|
+
screenshots: true,
|
|
242
|
+
snapshots: true,
|
|
243
|
+
sources: true
|
|
244
|
+
});
|
|
245
|
+
try {
|
|
246
|
+
await use(context);
|
|
247
|
+
} finally{
|
|
248
|
+
await context.tracing.stop({
|
|
249
|
+
path: (0, __rspack_external_node_path_806ed179.join)(traceDirectory, 'trace.zip')
|
|
250
|
+
});
|
|
251
|
+
const traceSummary = {
|
|
252
|
+
schemaVersion: 2,
|
|
253
|
+
runId: session.runId,
|
|
254
|
+
taskId: task.id,
|
|
255
|
+
mode: session.mode,
|
|
256
|
+
dataScope: session.dataScope,
|
|
257
|
+
packageId: session.packageId,
|
|
258
|
+
surfaceId: session.surfaceId,
|
|
259
|
+
sourceDigest: session.sourceDigest,
|
|
260
|
+
testBundleDigest: session.testBundleDigest,
|
|
261
|
+
networkPolicyRole: 'diagnostic-only',
|
|
262
|
+
tracePath: 'trace.zip',
|
|
263
|
+
traceRedacted: false,
|
|
264
|
+
traceSensitivity: 'raw-sensitive',
|
|
265
|
+
traceAccessScope: 'channel-only',
|
|
266
|
+
traceMayContain: [
|
|
267
|
+
'authorization headers',
|
|
268
|
+
'cookies',
|
|
269
|
+
'credential values',
|
|
270
|
+
'network request and response bodies',
|
|
271
|
+
'DOM snapshots',
|
|
272
|
+
'screenshots',
|
|
273
|
+
'test sources'
|
|
274
|
+
]
|
|
275
|
+
};
|
|
276
|
+
await Promise.all([
|
|
277
|
+
(0, __rspack_external_node_fs_promises_3b710708.writeFile)((0, __rspack_external_node_path_806ed179.join)(traceDirectory, 'trace-summary.json'), `${JSON.stringify(traceSummary, void 0, 2)}\n`, 'utf8'),
|
|
278
|
+
(0, __rspack_external_node_fs_promises_3b710708.writeFile)((0, __rspack_external_node_path_806ed179.join)(traceDirectory, 'debug.md'), [
|
|
279
|
+
'# Miniapp test trace',
|
|
280
|
+
'',
|
|
281
|
+
`- Run: \`${session.runId}\``,
|
|
282
|
+
`- Rstest task: \`${task.id}\``,
|
|
283
|
+
`- Mode: \`${session.mode}\``,
|
|
284
|
+
`- Data scope: \`${session.dataScope}\``,
|
|
285
|
+
`- Surface: \`${session.surfaceId}\``,
|
|
286
|
+
`- Source digest: \`${session.sourceDigest}\``,
|
|
287
|
+
`- Test bundle digest: \`${session.testBundleDigest}\``,
|
|
288
|
+
'- Browser network routing: diagnostic-only; not native egress enforcement',
|
|
289
|
+
'',
|
|
290
|
+
'`trace.zip` is raw, sensitive, channel-only evidence. It is not redacted and may contain authorization headers, cookies, credential values, network bodies, DOM snapshots, screenshots, and test source.',
|
|
291
|
+
'',
|
|
292
|
+
'Open the trace from The AI Platform Test Lab for debugging. Do not publish it or attach it to specialist context. The final assertion outcome is authoritative in `report.json` and `report.md`.',
|
|
293
|
+
''
|
|
294
|
+
].join('\n'), 'utf8')
|
|
295
|
+
]);
|
|
296
|
+
}
|
|
297
|
+
} finally{
|
|
298
|
+
hostPage.removeListener('websocket', diagnoseWebSocket);
|
|
299
|
+
await context.unroute('**/*', enforceDiagnosticNetworkPolicy);
|
|
199
300
|
}
|
|
200
301
|
},
|
|
201
302
|
page: async ({ context }, use)=>{
|
|
@@ -214,28 +315,10 @@ const test = __rspack_external__rstest_playwright_5734ff29.test.extend({
|
|
|
214
315
|
await use(fixtureMetadata(await loadSession(), page));
|
|
215
316
|
}
|
|
216
317
|
});
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const url = new URL(value);
|
|
220
|
-
if (matchesNonNetworkScheme(url.protocol)) return;
|
|
221
|
-
return url.origin;
|
|
222
|
-
} catch {
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
function matchesNonNetworkScheme(protocol) {
|
|
227
|
-
return NON_NETWORK_PROTOCOLS.has(protocol);
|
|
228
|
-
}
|
|
229
|
-
const NON_NETWORK_PROTOCOLS = new Set([
|
|
230
|
-
'about:',
|
|
231
|
-
'blob:',
|
|
232
|
-
'data:',
|
|
233
|
-
'file:'
|
|
234
|
-
]);
|
|
318
|
+
const afterEach = __rspack_external__rstest_playwright_5734ff29.afterEach;
|
|
319
|
+
const beforeEach = __rspack_external__rstest_playwright_5734ff29.beforeEach;
|
|
235
320
|
var afterAll = __rspack_external__rstest_playwright_5734ff29.afterAll;
|
|
236
|
-
var afterEach = __rspack_external__rstest_playwright_5734ff29.afterEach;
|
|
237
321
|
var beforeAll = __rspack_external__rstest_playwright_5734ff29.beforeAll;
|
|
238
|
-
var beforeEach = __rspack_external__rstest_playwright_5734ff29.beforeEach;
|
|
239
322
|
var describe = __rspack_external__rstest_playwright_5734ff29.describe;
|
|
240
323
|
var expect = __rspack_external__rstest_playwright_5734ff29.expect;
|
|
241
324
|
export { afterAll, afterEach, beforeAll, beforeEach, describe, expect, parseTapMiniappTestSession, test };
|