mirror of
https://github.com/bitwarden/browser
synced 2026-02-12 06:23:38 +00:00
Create queries for grabbing data from the server
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
export * from "./play";
|
||||
export * from "./scene";
|
||||
export * from "./scene-templates";
|
||||
export { test } from "./test";
|
||||
export * from "./queries";
|
||||
|
||||
57
libs/playwright-helpers/src/play.ts
Normal file
57
libs/playwright-helpers/src/play.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Query } from "./queries/query";
|
||||
import {
|
||||
SceneOptions,
|
||||
Scene,
|
||||
SCENE_OPTIONS_DEFAULTS,
|
||||
seedIdsToTearDown,
|
||||
seedIdsToWarnAbout,
|
||||
} from "./scene";
|
||||
import { SceneTemplate } from "./scene-templates/scene-template";
|
||||
|
||||
export class Play {
|
||||
/**
|
||||
* Runs server-side code to create a test scene and automatically destroys the scene when disposed.
|
||||
*
|
||||
* Scenes also expose a `mangle` method that can be used to mangle magic string in the same way the server reports them
|
||||
* back to avoid collisions. For example, if a scene creates a user with the email `test@example.com`, you can call
|
||||
* `scene.mangle("test@example.com")` to get the actual email address of the user created in the scene.
|
||||
*
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* import { Play, SingleUserScene } from "@bitwarden/playwright-helpers";
|
||||
*
|
||||
* test("my test", async ({ page }) => {
|
||||
* using scene = await Play.scene(new SingleUserScene({ email: "
|
||||
* expect(scene.mangle("my-id")).not.toBe("my-id");
|
||||
* });
|
||||
*
|
||||
* @param template The template to run to create the scene
|
||||
* @param options Options for the scene
|
||||
* @returns
|
||||
*/
|
||||
static async scene<TUp, TResult>(
|
||||
template: SceneTemplate<TUp, TResult>,
|
||||
options: SceneOptions = {},
|
||||
): Promise<Scene<TResult>> {
|
||||
const opts = { ...SCENE_OPTIONS_DEFAULTS, ...options };
|
||||
if (opts.noDown && process.env.CI) {
|
||||
throw new Error("Cannot set noDown to true in CI environments");
|
||||
}
|
||||
const scene = new Scene<TResult>(opts);
|
||||
await scene.init(template);
|
||||
if (!opts.noDown) {
|
||||
seedIdsToTearDown.add(scene.seedId);
|
||||
} else {
|
||||
seedIdsToWarnAbout.add(scene.seedId);
|
||||
}
|
||||
return scene;
|
||||
}
|
||||
|
||||
static async DeleteAllScenes(): Promise<void> {
|
||||
await Scene.DeleteAllScenes();
|
||||
}
|
||||
|
||||
static async query<TUp, TReturns>(template: Query<TUp, TReturns>): Promise<TReturns> {
|
||||
return await template.fetch();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Query } from "./query";
|
||||
|
||||
export class EmergencyAccessInviteQuery extends Query<
|
||||
{
|
||||
email: string;
|
||||
},
|
||||
string[]
|
||||
> {
|
||||
template: string = "EmergencyAccessInviteQuery";
|
||||
}
|
||||
5
libs/playwright-helpers/src/queries/index.ts
Normal file
5
libs/playwright-helpers/src/queries/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
/**
|
||||
* Scene Templates represent server-side seed scenes to create data for tests.
|
||||
*/
|
||||
|
||||
export * from "./emergency-access-invite.query";
|
||||
33
libs/playwright-helpers/src/queries/query.ts
Normal file
33
libs/playwright-helpers/src/queries/query.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { webServerBaseUrl } from "@playwright-config";
|
||||
|
||||
// First seed points at the seeder API proxy, second is the query path of the QueryController
|
||||
const queryApiUrl = new URL("/seed/query", webServerBaseUrl).toString();
|
||||
|
||||
export abstract class Query<TUp, TReturns> {
|
||||
abstract template: string;
|
||||
|
||||
constructor(private upArgs: TUp) {}
|
||||
async fetch(): Promise<TReturns> {
|
||||
const result = await queryFetch<TUp, TReturns>(this.template, this.upArgs);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
async function queryFetch<TUp, TReturns>(template: string, args: TUp): Promise<TReturns> {
|
||||
const response = await fetch(queryApiUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
template: template,
|
||||
arguments: args,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to run query: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return (await response.json()) as TReturns;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { SceneTemplate } from "./scene-template";
|
||||
|
||||
export class EmergencyAccessInviteQuery extends SceneTemplate<{
|
||||
email: string;
|
||||
}> {
|
||||
template: string = "EmergencyAccessInviteQuery";
|
||||
}
|
||||
@@ -2,5 +2,4 @@
|
||||
* Scene Templates represent server-side seed scenes to create data for tests.
|
||||
*/
|
||||
|
||||
export * from "./emergency-access-invite.scene";
|
||||
export * from "./single-user.scene";
|
||||
|
||||
@@ -113,6 +113,16 @@ export class Scene<Returns = void> implements UsingRequired {
|
||||
this.mangledMap = new Map(Object.entries(result.mangleMap));
|
||||
this._returnValue = result.result as unknown as Returns;
|
||||
}
|
||||
|
||||
static async DeleteAllScenes(): Promise<void> {
|
||||
const response = await fetch(seedApiUrl, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to delete scenes: ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type SceneOptions = {
|
||||
@@ -134,63 +144,13 @@ export type SceneOptions = {
|
||||
downAfterAll?: boolean;
|
||||
};
|
||||
|
||||
const SCENE_OPTIONS_DEFAULTS: Readonly<SceneOptions> = Object.freeze({
|
||||
export const SCENE_OPTIONS_DEFAULTS: Readonly<SceneOptions> = Object.freeze({
|
||||
noDown: false,
|
||||
downAfterAll: false,
|
||||
});
|
||||
|
||||
export class Play {
|
||||
/**
|
||||
* Runs server-side code to create a test scene and automatically destroys the scene when disposed.
|
||||
*
|
||||
* Scenes also expose a `mangle` method that can be used to mangle magic string in the same way the server reports them
|
||||
* back to avoid collisions. For example, if a scene creates a user with the email `test@example.com`, you can call
|
||||
* `scene.mangle("test@example.com")` to get the actual email address of the user created in the scene.
|
||||
*
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* import { Play, SingleUserScene } from "@bitwarden/playwright-helpers";
|
||||
*
|
||||
* test("my test", async ({ page }) => {
|
||||
* using scene = await Play.scene(new SingleUserScene({ email: "
|
||||
* expect(scene.mangle("my-id")).not.toBe("my-id");
|
||||
* });
|
||||
*
|
||||
* @param template The template to run to create the scene
|
||||
* @param options Options for the scene
|
||||
* @returns
|
||||
*/
|
||||
static async scene<TUp, TResult>(
|
||||
template: SceneTemplate<TUp, TResult>,
|
||||
options: SceneOptions = {},
|
||||
): Promise<Scene<TResult>> {
|
||||
const opts = { ...SCENE_OPTIONS_DEFAULTS, ...options };
|
||||
if (opts.noDown && process.env.CI) {
|
||||
throw new Error("Cannot set noDown to true in CI environments");
|
||||
}
|
||||
const scene = new Scene<TResult>(opts);
|
||||
await scene.init(template);
|
||||
if (!opts.noDown) {
|
||||
seedIdsToTearDown.add(scene.seedId);
|
||||
} else {
|
||||
seedIdsToWarnAbout.add(scene.seedId);
|
||||
}
|
||||
return scene;
|
||||
}
|
||||
|
||||
static async DeleteAllScenes(): Promise<void> {
|
||||
const response = await fetch(seedApiUrl, {
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to delete scenes: ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const seedIdsToTearDown = new Set<string>();
|
||||
const seedIdsToWarnAbout = new Set<string>();
|
||||
export const seedIdsToTearDown = new Set<string>();
|
||||
export const seedIdsToWarnAbout = new Set<string>();
|
||||
|
||||
// After all tests complete
|
||||
test.afterAll(async () => {
|
||||
|
||||
Reference in New Issue
Block a user