mirror of
https://github.com/bitwarden/browser
synced 2026-01-30 16:23:53 +00:00
Create playwright scenes framework
This is a client-side implementation of the db recipes seeding framework for bitwarden server
This commit is contained in:
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -219,3 +219,4 @@ apps/web/src/locales/en/messages.json
|
||||
**/jest.config.js @bitwarden/team-platform-dev
|
||||
**/project.jsons @bitwarden/team-platform-dev
|
||||
libs/pricing @bitwarden/team-billing-dev
|
||||
libs/playwright-scenes @bitwarden/team-architecture
|
||||
|
||||
@@ -59,6 +59,7 @@ module.exports = {
|
||||
"<rootDir>/libs/tools/send/send-ui/jest.config.js",
|
||||
"<rootDir>/libs/user-core/jest.config.js",
|
||||
"<rootDir>/libs/vault/jest.config.js",
|
||||
"<rootDir>/libs/playwright-scenes/jest.config.js",
|
||||
],
|
||||
|
||||
// Workaround for a memory leak that crashes tests in CI:
|
||||
|
||||
5
libs/playwright-scenes/README.md
Normal file
5
libs/playwright-scenes/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# playwright-scenes
|
||||
|
||||
Owned by: architecture
|
||||
|
||||
Framework for writing end-to-end playwright tests
|
||||
3
libs/playwright-scenes/eslint.config.mjs
Normal file
3
libs/playwright-scenes/eslint.config.mjs
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from "../../eslint.config.mjs";
|
||||
|
||||
export default [...baseConfig];
|
||||
13
libs/playwright-scenes/jest.config.js
Normal file
13
libs/playwright-scenes/jest.config.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const sharedConfig = require("../../libs/shared/jest.config.angular");
|
||||
|
||||
module.exports = {
|
||||
...sharedConfig,
|
||||
displayName: "playwright-scenes",
|
||||
preset: "../../jest.preset.js",
|
||||
testEnvironment: "node",
|
||||
transform: {
|
||||
"^.+\\.[tj]s$": ["ts-jest", { tsconfig: "<rootDir>/tsconfig.spec.json" }],
|
||||
},
|
||||
moduleFileExtensions: ["ts", "js", "html"],
|
||||
coverageDirectory: "../../coverage/libs/playwright-scenes",
|
||||
};
|
||||
11
libs/playwright-scenes/package.json
Normal file
11
libs/playwright-scenes/package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@bitwarden/playwright-scenes",
|
||||
"version": "0.0.1",
|
||||
"description": "Framework for writing end-to-end playwright tests",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"license": "GPL-3.0",
|
||||
"author": "platform"
|
||||
}
|
||||
40
libs/playwright-scenes/project.json
Normal file
40
libs/playwright-scenes/project.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "playwright-scenes",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/playwright-scenes/src",
|
||||
"projectType": "library",
|
||||
"tags": ["!dependsOn:common"],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/playwright-scenes",
|
||||
"main": "libs/playwright-scenes/src/index.ts",
|
||||
"tsConfig": "libs/playwright-scenes/tsconfig.lib.json",
|
||||
"assets": ["libs/playwright-scenes/*.md"],
|
||||
"rootDir": "libs/playwright-scenes/src"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/playwright-scenes/**/*.ts"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "libs/playwright-scenes/jest.config.js"
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"command": "npx playwright test"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
libs/playwright-scenes/src/index.ts
Normal file
2
libs/playwright-scenes/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./scene";
|
||||
export * from "./recipes";
|
||||
1
libs/playwright-scenes/src/recipes/index.ts
Normal file
1
libs/playwright-scenes/src/recipes/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./organization-with-users.recipe";
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Recipe } from "./recipe";
|
||||
|
||||
export class OrganizationWithUsersRecipe extends Recipe<{
|
||||
name: string;
|
||||
numUsers: number;
|
||||
domain: string;
|
||||
}> {
|
||||
template: string = "OrganizationWithUsersRecipe";
|
||||
}
|
||||
52
libs/playwright-scenes/src/recipes/recipe.ts
Normal file
52
libs/playwright-scenes/src/recipes/recipe.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import * as playwrightConfig from "../../../../playwright.config";
|
||||
|
||||
const { webServer } = playwrightConfig.default as { webServer: { url: string } };
|
||||
|
||||
export abstract class Recipe<TUp> {
|
||||
abstract template: string;
|
||||
private seedId?: string;
|
||||
|
||||
constructor(private upArgs: TUp) {}
|
||||
async up(): Promise<Record<string, string>> {
|
||||
const response = await fetch(`${webServer.url}/api/seed`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
template: this.template,
|
||||
arguments: this.upArgs,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to seed recipe: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = JSON.parse(await response.json()) as SeedResult;
|
||||
this.seedId = result.seedId;
|
||||
return result.mangleMap;
|
||||
}
|
||||
|
||||
async down(): Promise<void> {
|
||||
const response = await fetch(`${webServer.url}/api/delete`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
template: this.template,
|
||||
seedId: this.seedId,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to delete recipe: ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface SeedResult {
|
||||
mangleMap: Record<string, string>;
|
||||
seedId: string;
|
||||
}
|
||||
66
libs/playwright-scenes/src/scene.ts
Normal file
66
libs/playwright-scenes/src/scene.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { UsingRequired } from "@bitwarden/common/platform/misc/using-required";
|
||||
|
||||
import { OrganizationWithUsersRecipe } from "./recipes/organization-with-users.recipe";
|
||||
import { Recipe } from "./recipes/recipe";
|
||||
|
||||
export class Scene implements UsingRequired {
|
||||
private inited = false;
|
||||
private recipe?: Recipe<unknown>;
|
||||
private mangledMap = new Map<string, string>();
|
||||
|
||||
[Symbol.dispose] = () => {
|
||||
if (!this.inited) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.recipe) {
|
||||
throw new Error("Scene was not properly initialized");
|
||||
}
|
||||
|
||||
// Fire off an unawaited promise to delete the side effects of the scene
|
||||
void this.recipe.down();
|
||||
};
|
||||
|
||||
mangle(id: string): string {
|
||||
if (!this.inited) {
|
||||
throw new Error("Scene must be initialized before mangling ids");
|
||||
}
|
||||
|
||||
return this.mangledMap.get(id) ?? id;
|
||||
}
|
||||
|
||||
async init<T extends Recipe<TUp>, TUp>(recipe: T): Promise<void> {
|
||||
if (this.inited) {
|
||||
throw new Error("Scene has already been initialized");
|
||||
}
|
||||
this.recipe = recipe;
|
||||
this.inited = true;
|
||||
|
||||
const response = await recipe.up();
|
||||
|
||||
this.mangledMap = new Map(Object.entries(response.mangleMap));
|
||||
}
|
||||
}
|
||||
|
||||
export class Play {
|
||||
static async scene<T extends Recipe<TUp>, TUp>(recipe: T): Promise<Scene> {
|
||||
const scene = new Scene();
|
||||
await scene.init(recipe);
|
||||
return scene;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- example usage of the framework
|
||||
async function test() {
|
||||
// example usage
|
||||
const recipe = new OrganizationWithUsersRecipe({
|
||||
name: "My Org",
|
||||
numUsers: 3,
|
||||
domain: "example.com",
|
||||
});
|
||||
using scene = await Play.scene(recipe);
|
||||
|
||||
expect(scene.mangle("my-id")).toBe("my-id");
|
||||
}
|
||||
6
libs/playwright-scenes/tsconfig.eslint.json
Normal file
6
libs/playwright-scenes/tsconfig.eslint.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [],
|
||||
"include": ["src/**/*.ts", "src/**/*.js"],
|
||||
"exclude": ["**/build", "**/dist"]
|
||||
}
|
||||
13
libs/playwright-scenes/tsconfig.json
Normal file
13
libs/playwright-scenes/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
10
libs/playwright-scenes/tsconfig.lib.json
Normal file
10
libs/playwright-scenes/tsconfig.lib.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["jest.config.js", "src/**/*.spec.ts"]
|
||||
}
|
||||
10
libs/playwright-scenes/tsconfig.spec.json
Normal file
10
libs/playwright-scenes/tsconfig.spec.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node10",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": ["jest.config", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"]
|
||||
}
|
||||
9
package-lock.json
generated
9
package-lock.json
generated
@@ -395,6 +395,11 @@
|
||||
"version": "0.0.0",
|
||||
"license": "GPL-3.0"
|
||||
},
|
||||
"libs/playwright-scenes": {
|
||||
"name": "@bitwarden/playwright-scenes",
|
||||
"version": "0.0.1",
|
||||
"license": "GPL-3.0"
|
||||
},
|
||||
"libs/pricing": {
|
||||
"name": "@bitwarden/pricing",
|
||||
"version": "0.0.0",
|
||||
@@ -4685,6 +4690,10 @@
|
||||
"resolved": "libs/platform",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bitwarden/playwright-scenes": {
|
||||
"resolved": "libs/playwright-scenes",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@bitwarden/pricing": {
|
||||
"resolved": "libs/pricing",
|
||||
"link": true
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"@bitwarden/nx-plugin": ["libs/nx-plugin/src/index.ts"],
|
||||
"@bitwarden/platform": ["./libs/platform/src"],
|
||||
"@bitwarden/platform/*": ["./libs/platform/src/*"],
|
||||
"@bitwarden/playwright-scenes": ["libs/playwright-scenes/src/index.ts"],
|
||||
"@bitwarden/pricing": ["libs/pricing/src/index.ts"],
|
||||
"@bitwarden/send-ui": ["./libs/tools/send/send-ui/src"],
|
||||
"@bitwarden/serialization": ["libs/serialization/src/index.ts"],
|
||||
|
||||
Reference in New Issue
Block a user