mirror of
https://github.com/Kong/insomnia.git
synced 2026-11-02 03:00:25 -05:00
feat: add step instrumentation for flow methods
- Introduced a new `step` decorator to enhance method reporting in flows. - Removed the old step instrumentation logic from `step-instrumentation.ts`. - Updated various flow classes to utilize the new `@step()` decorator for method tracking. - Simplified page manager by removing the instrumentWithSteps function. - Adjusted tests to ensure compatibility with the new step reporting mechanism.
This commit is contained in:
28 files changed
+353
-315
No files matched your search
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"name": "insomnia-data",
|
||||
"license": "Apache-2.0",
|
||||
"version": "13.3.1",
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
trackHiddenWindowCoverage,
|
||||
} from "../misc/coverage";
|
||||
import { launchInsomniaElectron } from "../misc/launch";
|
||||
import { step } from "../misc/step";
|
||||
import { BaseFlow } from "./base.flow";
|
||||
|
||||
export class AppFlow extends BaseFlow {
|
||||
@@ -18,6 +19,7 @@ export class AppFlow extends BaseFlow {
|
||||
* `flowManager`/`pageManager` onto the new app in place, so the same
|
||||
* `user.flowManager`/`user.pageManager` instances keep working.
|
||||
*/
|
||||
@step()
|
||||
async restart(): Promise<Page> {
|
||||
const config = this.flowManager.launchConfig;
|
||||
if (!config) {
|
||||
@@ -61,6 +63,7 @@ export class AppFlow extends BaseFlow {
|
||||
* `setImmediate` before returning, so the error has been fully
|
||||
* handled by the time this resolves.
|
||||
*/
|
||||
@step()
|
||||
async emitStdoutEpipeError(): Promise<void> {
|
||||
await this.flowManager.electronApp!.evaluate(() => {
|
||||
process.nextTick(() => {
|
||||
@@ -79,6 +82,7 @@ export class AppFlow extends BaseFlow {
|
||||
* event on the main process's `process.stdout` (e.g. `"error"`,
|
||||
* `"close"`, `"drain"`).
|
||||
*/
|
||||
@step()
|
||||
async getStdoutListenerCount(event: string): Promise<number> {
|
||||
return this.flowManager.electronApp!.evaluate(
|
||||
(_electron, evt) => process.stdout.listenerCount(evt),
|
||||
@@ -91,6 +95,7 @@ export class AppFlow extends BaseFlow {
|
||||
* trivial `evaluate()` call. If the main process had crashed, this
|
||||
* call itself would reject with a "Target closed" error.
|
||||
*/
|
||||
@step()
|
||||
async isAlive(): Promise<boolean> {
|
||||
return this.flowManager.electronApp!.evaluate(() => true);
|
||||
}
|
||||
@@ -99,6 +104,7 @@ export class AppFlow extends BaseFlow {
|
||||
* Reads the main process's `userData` directory — the same path
|
||||
* Insomnia backs up its NeDB `.db` files under (`<dataPath>/backups`).
|
||||
*/
|
||||
@step()
|
||||
async getDataPath(): Promise<string> {
|
||||
return this.flowManager.electronApp!.evaluate(({ app }) =>
|
||||
app.getPath("userData"),
|
||||
@@ -112,6 +118,7 @@ export class AppFlow extends BaseFlow {
|
||||
* "check for remote changes" logic on a real focus change, without
|
||||
* actually needing to blur/refocus the Electron window from the OS.
|
||||
*/
|
||||
@step()
|
||||
async simulateMainWindowFocusChange(): Promise<void> {
|
||||
await this.flowManager.electronApp!.evaluate(({ BrowserWindow }) => {
|
||||
for (const win of BrowserWindow.getAllWindows()) {
|
||||
@@ -127,6 +134,7 @@ export class AppFlow extends BaseFlow {
|
||||
* them in order. Only lines written during this call are captured —
|
||||
* nothing from before it started.
|
||||
*/
|
||||
@step()
|
||||
async captureMainProcessLog(action: () => Promise<void>): Promise<string[]> {
|
||||
const lines: string[] = [];
|
||||
const proc = this.flowManager.electronApp!.process();
|
||||
@@ -153,6 +161,7 @@ export class AppFlow extends BaseFlow {
|
||||
* to run.
|
||||
* @throws If no window with that title is currently open
|
||||
*/
|
||||
@step()
|
||||
async closeHiddenScriptWindow(): Promise<void> {
|
||||
for (const win of await this.flowManager.electronApp!.windows()) {
|
||||
if ((await win.title().catch(() => "")) === "Hidden Browser Window") {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { step } from "../misc/step";
|
||||
import type { ClientCertificate } from "../models/client-certificate";
|
||||
import type { Collection } from "../models/collection";
|
||||
import type { EventStreamRequest } from "../models/event-stream-request";
|
||||
@@ -25,6 +26,7 @@ export class CertificatesFlow extends BaseFlow {
|
||||
* Certificates" dialog from `item`'s location. Overwrites any CA
|
||||
* certificate already set.
|
||||
*/
|
||||
@step()
|
||||
async setCaCertificate(item: CertificateTarget, path: string): Promise<void> {
|
||||
await this.openTarget(item);
|
||||
|
||||
@@ -38,6 +40,7 @@ export class CertificatesFlow extends BaseFlow {
|
||||
* Adds a client certificate to `item`'s workspace, opening the "Manage
|
||||
* Certificates" dialog from `item`'s location.
|
||||
*/
|
||||
@step()
|
||||
async addClientCertificate(
|
||||
item: CertificateTarget,
|
||||
certificate: ClientCertificate,
|
||||
@@ -55,6 +58,7 @@ export class CertificatesFlow extends BaseFlow {
|
||||
* its `host`, opening the "Manage Certificates" dialog from `item`'s
|
||||
* location.
|
||||
*/
|
||||
@step()
|
||||
async setClientCertificateEnabled(
|
||||
item: CertificateTarget,
|
||||
host: string,
|
||||
@@ -78,6 +82,7 @@ export class CertificatesFlow extends BaseFlow {
|
||||
* Toggles `item`'s workspace-wide CA Certificate's Enabled/Disabled
|
||||
* state, opening the "Manage Certificates" dialog from `item`'s location.
|
||||
*/
|
||||
@step()
|
||||
async setCaCertificateEnabled(
|
||||
item: CertificateTarget,
|
||||
enabled: boolean,
|
||||
@@ -94,6 +99,7 @@ export class CertificatesFlow extends BaseFlow {
|
||||
* Toggles an MCP Client's "SSL Certificate Validation" switch, opening
|
||||
* the "Manage Certificates" dialog from the client's location.
|
||||
*/
|
||||
@step()
|
||||
async setSslValidationEnabled(
|
||||
client: McpClient,
|
||||
enabled: boolean,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { DeleteMode } from "../enums/delete-mode";
|
||||
import { step } from "../misc/step";
|
||||
import type { Project } from "../models/project";
|
||||
import { BaseFlow } from "./base.flow";
|
||||
|
||||
@@ -8,6 +9,7 @@ export class CloudSyncFlow extends BaseFlow {
|
||||
* where synced, local-only, and unsynced remote files are all shown as
|
||||
* cards side by side.
|
||||
*/
|
||||
@step()
|
||||
async openProjectDashboard(project: Project): Promise<void> {
|
||||
const { workspacePage, cloudSyncPage } = this.pageManager;
|
||||
const node = await workspacePage.requireItemNode({ name: project.name });
|
||||
@@ -19,6 +21,7 @@ export class CloudSyncFlow extends BaseFlow {
|
||||
* Fetches a workspace that only exists on the Cloud Sync backend into
|
||||
* the local sidebar and navigates into it.
|
||||
*/
|
||||
@step()
|
||||
async fetch(name: string): Promise<void> {
|
||||
const { cloudSyncPage } = this.pageManager;
|
||||
await cloudSyncPage.backToAllProjects();
|
||||
@@ -29,6 +32,7 @@ export class CloudSyncFlow extends BaseFlow {
|
||||
* Discards every uncommitted local change on the current Cloud-synced
|
||||
* workspace.
|
||||
*/
|
||||
@step()
|
||||
async discardAllChanges(): Promise<void> {
|
||||
const { cloudSyncPage } = this.pageManager;
|
||||
await cloudSyncPage.discardAllChanges();
|
||||
@@ -37,6 +41,7 @@ export class CloudSyncFlow extends BaseFlow {
|
||||
/**
|
||||
* Stages `requestName`, commits with `message`, then pushes.
|
||||
*/
|
||||
@step()
|
||||
async commitAndPush(requestName: string, message: string): Promise<void> {
|
||||
const { cloudSyncPage } = this.pageManager;
|
||||
await cloudSyncPage.openCommitDialog();
|
||||
@@ -46,6 +51,7 @@ export class CloudSyncFlow extends BaseFlow {
|
||||
/**
|
||||
* Stages `requestName` and commits with `message`, without pushing.
|
||||
*/
|
||||
@step()
|
||||
async commit(requestName: string, message: string): Promise<void> {
|
||||
const { cloudSyncPage } = this.pageManager;
|
||||
await cloudSyncPage.openCommitDialog();
|
||||
@@ -56,6 +62,7 @@ export class CloudSyncFlow extends BaseFlow {
|
||||
* Restores the snapshot with commit message `message` from the current
|
||||
* workspace's History.
|
||||
*/
|
||||
@step()
|
||||
async restoreSnapshot(message: string): Promise<void> {
|
||||
const { cloudSyncPage } = this.pageManager;
|
||||
await cloudSyncPage.openHistory();
|
||||
@@ -67,6 +74,7 @@ export class CloudSyncFlow extends BaseFlow {
|
||||
* Creates a new branch named `branch` off whichever branch is
|
||||
* currently checked out on the current Cloud-synced workspace.
|
||||
*/
|
||||
@step()
|
||||
async createBranch(branch: string): Promise<void> {
|
||||
const { cloudSyncPage } = this.pageManager;
|
||||
await cloudSyncPage.openBranchesDialog();
|
||||
@@ -78,6 +86,7 @@ export class CloudSyncFlow extends BaseFlow {
|
||||
* Merges `branch` into whichever branch is currently checked out on
|
||||
* the current Cloud-synced workspace.
|
||||
*/
|
||||
@step()
|
||||
async mergeBranch(branch: string): Promise<void> {
|
||||
const { cloudSyncPage } = this.pageManager;
|
||||
await cloudSyncPage.openBranchesDialog();
|
||||
@@ -89,6 +98,7 @@ export class CloudSyncFlow extends BaseFlow {
|
||||
* Merges `branch` into whichever branch is currently checked out,
|
||||
* expecting conflicts, and resolves all of them by taking the same side.
|
||||
*/
|
||||
@step()
|
||||
async mergeBranchResolvingConflicts(
|
||||
branch: string,
|
||||
version: "Current" | "Incoming",
|
||||
@@ -107,6 +117,7 @@ export class CloudSyncFlow extends BaseFlow {
|
||||
* afterward) or "Delete Permanently" (removed both locally and on the
|
||||
* backend).
|
||||
*/
|
||||
@step()
|
||||
async delete(name: string, mode: DeleteMode): Promise<void> {
|
||||
const { cloudSyncPage } = this.pageManager;
|
||||
await cloudSyncPage.openDeleteWorkspaceDialog(name);
|
||||
@@ -119,6 +130,7 @@ export class CloudSyncFlow extends BaseFlow {
|
||||
* archiving the backend project by its own id (no local pull required).
|
||||
* Requires `openProjectDashboard()` to have already navigated there.
|
||||
*/
|
||||
@step()
|
||||
async deleteUnsyncedFile(name: string): Promise<void> {
|
||||
const { cloudSyncPage } = this.pageManager;
|
||||
await cloudSyncPage.openFileDeleteDialog(name);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { step } from "../misc/step";
|
||||
import type { Collection } from "../models/collection";
|
||||
import {
|
||||
Environment,
|
||||
@@ -31,6 +32,7 @@ export class EnvironmentFlow extends BaseFlow {
|
||||
* Creates/renames the workspace's Base Environment under `parent`, then
|
||||
* applies `environment.kvPairData`.
|
||||
*/
|
||||
@step()
|
||||
async createBaseEnvironment(parent: Project, environment: Environment): Promise<Environment> {
|
||||
await this.workspaceFlow.createEnvironment(parent, environment);
|
||||
if (environment.name !== BASE_ENVIRONMENT_NAME) {
|
||||
@@ -47,6 +49,7 @@ export class EnvironmentFlow extends BaseFlow {
|
||||
* Creates a sub-environment nested under `parent`, then applies
|
||||
* `environment.kvPairData`.
|
||||
*/
|
||||
@step()
|
||||
async createSubEnvironment(parent: Environment, environment: Environment): Promise<Environment> {
|
||||
await this.pageManager.environmentPage.createSubEnvironment(
|
||||
environment.name,
|
||||
@@ -59,6 +62,7 @@ export class EnvironmentFlow extends BaseFlow {
|
||||
/**
|
||||
* Finds an environment by name/id and reads its variables back.
|
||||
*/
|
||||
@step()
|
||||
async get(
|
||||
item: string | { name: string; id?: string },
|
||||
): Promise<Environment | undefined> {
|
||||
@@ -88,6 +92,7 @@ export class EnvironmentFlow extends BaseFlow {
|
||||
* Links a project Environment into `item` via the "Select a Project
|
||||
* Environment" picker.
|
||||
*/
|
||||
@step()
|
||||
async link(
|
||||
item:
|
||||
| Collection
|
||||
@@ -123,6 +128,7 @@ export class EnvironmentFlow extends BaseFlow {
|
||||
* Distinct from `link()`, which instead attaches a separate Project
|
||||
* Environment; both can be active on the same collection at once.
|
||||
*/
|
||||
@step()
|
||||
async activate(collection: Collection, name: string): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const node = await workspace.resolveNode(collection);
|
||||
@@ -134,6 +140,7 @@ export class EnvironmentFlow extends BaseFlow {
|
||||
* Opens one environment picker on `collection`'s toolbar and reports
|
||||
* which picker popovers are open, then dismisses it.
|
||||
*/
|
||||
@step()
|
||||
async getOpenPickersAfterOpening(
|
||||
collection: Collection,
|
||||
picker: "project" | "collection",
|
||||
@@ -153,6 +160,7 @@ export class EnvironmentFlow extends BaseFlow {
|
||||
* Creates a Project Environment from `collection`'s project picker and
|
||||
* reads the picker back, without linking the new environment.
|
||||
*/
|
||||
@step()
|
||||
async addProjectEnvironmentFromPicker(
|
||||
collection: Collection,
|
||||
name: string,
|
||||
@@ -170,6 +178,7 @@ export class EnvironmentFlow extends BaseFlow {
|
||||
* Creates a shared or private sub-environment inside `collection`'s own
|
||||
* "Manage Environments" modal and reports whether it is marked private.
|
||||
*/
|
||||
@step()
|
||||
async createCollectionSubEnvironment(
|
||||
collection: Collection,
|
||||
environment: Environment,
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { RequestAuthentication } from "insomnia-data";
|
||||
|
||||
import { ContextMenuItem } from "../enums/context-menu-items";
|
||||
import { TreeNodeType } from "../enums/tree-node-types";
|
||||
import { step } from "../misc/step";
|
||||
import type { Collection } from "../models/collection";
|
||||
import { EventStreamRequest } from "../models/event-stream-request";
|
||||
import type { Folder } from "../models/folder";
|
||||
@@ -15,6 +16,7 @@ export class EventStreamRequestFlow extends BaseFlow {
|
||||
* Creates an Event Stream request under `parent` via the standalone
|
||||
* project tree, then verifies it was created.
|
||||
*/
|
||||
@step()
|
||||
async create(
|
||||
parent: Collection | Folder,
|
||||
request: EventStreamRequest,
|
||||
@@ -45,6 +47,7 @@ export class EventStreamRequestFlow extends BaseFlow {
|
||||
* Reads back an existing Event Stream request's fields via the
|
||||
* standalone project tree.
|
||||
*/
|
||||
@step()
|
||||
async get(
|
||||
item: string | { name: string; id?: string },
|
||||
parent?: Collection,
|
||||
@@ -71,6 +74,7 @@ export class EventStreamRequestFlow extends BaseFlow {
|
||||
* Opens `request`, clicks its Auth tab's token-fetch button and reads
|
||||
* the resulting tokens back.
|
||||
*/
|
||||
@step()
|
||||
async fetchOAuth2Tokens(request: EventStreamRequest): Promise<OAuth2Tokens> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const page = this.pageManager.eventStreamRequestPage;
|
||||
@@ -86,6 +90,7 @@ export class EventStreamRequestFlow extends BaseFlow {
|
||||
/**
|
||||
* Opens `request` and applies `authentication` on its Auth tab.
|
||||
*/
|
||||
@step()
|
||||
async setAuthentication(
|
||||
request: EventStreamRequest,
|
||||
authentication: RequestAuthentication,
|
||||
@@ -103,6 +108,7 @@ export class EventStreamRequestFlow extends BaseFlow {
|
||||
* Connects the event stream for `request` and reads back the resulting
|
||||
* response via the standalone project tree.
|
||||
*/
|
||||
@step()
|
||||
async send(
|
||||
request: EventStreamRequest,
|
||||
options?: { expectConnected?: boolean },
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { ApiSpecExportFormat } from "../enums/api-spec-export-format";
|
||||
import { ContextMenuItem } from "../enums/context-menu-items";
|
||||
import type { ExportFormat } from "../enums/export-format";
|
||||
import { step } from "../misc/step";
|
||||
import type { Collection } from "../models/collection";
|
||||
import type { Project } from "../models/project";
|
||||
import { BaseFlow } from "./base.flow";
|
||||
@@ -11,6 +12,7 @@ export class ExportFlow extends BaseFlow {
|
||||
* "Export" context-menu item, confirming the request-selection step
|
||||
* (every request stays checked).
|
||||
*/
|
||||
@step()
|
||||
async exportCollection(item: Collection, format: ExportFormat, path: string): Promise<void> {
|
||||
const { workspacePage, exportPage } = this.pageManager;
|
||||
const node = await workspacePage.resolveNode(item);
|
||||
@@ -28,6 +30,7 @@ export class ExportFlow extends BaseFlow {
|
||||
* each) via Preferences -> Data, selecting `format`. Skips the
|
||||
* request-selection step, which only Collection-scoped exports show.
|
||||
*/
|
||||
@step()
|
||||
async exportProject(item: Project, format: ExportFormat, path: string): Promise<void> {
|
||||
const { workspacePage, preferencesPage, exportPage } = this.pageManager;
|
||||
await workspacePage.clickNode(await workspacePage.resolveNode(item));
|
||||
@@ -47,6 +50,7 @@ export class ExportFlow extends BaseFlow {
|
||||
* workspace) via Preferences -> Data. Always writes as Insomnia v5 —
|
||||
* this entry point has no request-selection or format-selection step.
|
||||
*/
|
||||
@step()
|
||||
async exportAllData(directoryPath: string): Promise<void> {
|
||||
const { preferencesPage, exportPage } = this.pageManager;
|
||||
await exportPage.stubSaveDirectoryLocation(directoryPath);
|
||||
@@ -67,6 +71,7 @@ export class ExportFlow extends BaseFlow {
|
||||
* so this rejects with that alert's trimmed text in that case; dismiss
|
||||
* it with `workspaceFlow.closeDialog()` before continuing.
|
||||
*/
|
||||
@step()
|
||||
async exportOpenApiSpec(
|
||||
item: Collection,
|
||||
format: ApiSpecExportFormat,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { ElectronApplication } from "@playwright/test";
|
||||
|
||||
import { instrumentWithSteps } from "../misc/step-instrumentation";
|
||||
import type { PageManager } from "../pages/page-manager";
|
||||
import { AppFlow } from "./app.flow";
|
||||
import { CertificatesFlow } from "./certificates.flow";
|
||||
@@ -89,140 +88,96 @@ export class FlowManager implements FlowContext {
|
||||
}
|
||||
|
||||
get appFlow(): AppFlow {
|
||||
return (this._appFlow ??= instrumentWithSteps(
|
||||
new AppFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._appFlow ??= new AppFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get certificatesFlow(): CertificatesFlow {
|
||||
return (this._certificatesFlow ??= instrumentWithSteps(
|
||||
new CertificatesFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._certificatesFlow ??= new CertificatesFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get cloudSyncFlow(): CloudSyncFlow {
|
||||
return (this._cloudSyncFlow ??= instrumentWithSteps(
|
||||
new CloudSyncFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._cloudSyncFlow ??= new CloudSyncFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get cookieFlow(): CookieFlow {
|
||||
return (this._cookieFlow ??= instrumentWithSteps(
|
||||
new CookieFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._cookieFlow ??= new CookieFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get environmentFlow(): EnvironmentFlow {
|
||||
return (this._environmentFlow ??= instrumentWithSteps(
|
||||
new EnvironmentFlow(this, this.pageManager, this.workspaceFlow),
|
||||
));
|
||||
return (this._environmentFlow ??= new EnvironmentFlow(this, this.pageManager, this.workspaceFlow));
|
||||
}
|
||||
|
||||
get eventStreamRequestFlow(): EventStreamRequestFlow {
|
||||
return (this._eventStreamRequestFlow ??= instrumentWithSteps(
|
||||
new EventStreamRequestFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._eventStreamRequestFlow ??= new EventStreamRequestFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get exportFlow(): ExportFlow {
|
||||
return (this._exportFlow ??= instrumentWithSteps(
|
||||
new ExportFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._exportFlow ??= new ExportFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get folderFlow(): FolderFlow {
|
||||
return (this._folderFlow ??= instrumentWithSteps(
|
||||
new FolderFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._folderFlow ??= new FolderFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get gitSyncFlow(): GitSyncFlow {
|
||||
return (this._gitSyncFlow ??= instrumentWithSteps(
|
||||
new GitSyncFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._gitSyncFlow ??= new GitSyncFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get graphqlRequestFlow(): GraphQLRequestFlow {
|
||||
return (this._graphqlRequestFlow ??= instrumentWithSteps(
|
||||
new GraphQLRequestFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._graphqlRequestFlow ??= new GraphQLRequestFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get grpcRequestFlow(): GrpcRequestFlow {
|
||||
return (this._grpcRequestFlow ??= instrumentWithSteps(
|
||||
new GrpcRequestFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._grpcRequestFlow ??= new GrpcRequestFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get httpRequestFlow(): HttpRequestFlow {
|
||||
return (this._httpRequestFlow ??= instrumentWithSteps(
|
||||
new HttpRequestFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._httpRequestFlow ??= new HttpRequestFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get importFlow(): ImportFlow {
|
||||
return (this._importFlow ??= instrumentWithSteps(
|
||||
new ImportFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._importFlow ??= new ImportFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get mcpClientFlow(): McpClientFlow {
|
||||
return (this._mcpClientFlow ??= instrumentWithSteps(
|
||||
new McpClientFlow(this, this.pageManager, this.workspaceFlow),
|
||||
));
|
||||
return (this._mcpClientFlow ??= new McpClientFlow(this, this.pageManager, this.workspaceFlow));
|
||||
}
|
||||
|
||||
get mockServerFlow(): MockServerFlow {
|
||||
return (this._mockServerFlow ??= instrumentWithSteps(
|
||||
new MockServerFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._mockServerFlow ??= new MockServerFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get organizationFlow(): OrganizationFlow {
|
||||
return (this._organizationFlow ??= instrumentWithSteps(
|
||||
new OrganizationFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._organizationFlow ??= new OrganizationFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get preferencesFlow(): PreferencesFlow {
|
||||
return (this._preferencesFlow ??= instrumentWithSteps(
|
||||
new PreferencesFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._preferencesFlow ??= new PreferencesFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get socketIoRequestFlow(): SocketIORequestFlow {
|
||||
return (this._socketIoRequestFlow ??= instrumentWithSteps(
|
||||
new SocketIORequestFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._socketIoRequestFlow ??= new SocketIORequestFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get templateTagFlow(): TemplateTagFlow {
|
||||
return (this._templateTagFlow ??= instrumentWithSteps(
|
||||
new TemplateTagFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._templateTagFlow ??= new TemplateTagFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get variableSourceFlow(): VariableSourceFlow {
|
||||
return (this._variableSourceFlow ??= instrumentWithSteps(
|
||||
new VariableSourceFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._variableSourceFlow ??= new VariableSourceFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get webSocketRequestFlow(): WebSocketRequestFlow {
|
||||
return (this._webSocketRequestFlow ??= instrumentWithSteps(
|
||||
new WebSocketRequestFlow(this, this.pageManager),
|
||||
));
|
||||
return (this._webSocketRequestFlow ??= new WebSocketRequestFlow(this, this.pageManager));
|
||||
}
|
||||
|
||||
get workspaceFlow(): WorkspaceFlow {
|
||||
return (this._workspaceFlow ??= instrumentWithSteps(
|
||||
new WorkspaceFlow(
|
||||
return (this._workspaceFlow ??= new WorkspaceFlow(
|
||||
this,
|
||||
this.pageManager,
|
||||
this.preferencesFlow,
|
||||
this,
|
||||
this.gitRepoUrl,
|
||||
),
|
||||
));
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ContextMenuItem } from "../enums/context-menu-items";
|
||||
import { TreeNodeType } from "../enums/tree-node-types";
|
||||
import { step } from "../misc/step";
|
||||
import type { Collection } from "../models/collection";
|
||||
import { Folder } from "../models/folder";
|
||||
import type { OAuth2Tokens } from "../pages/auth-tab.page";
|
||||
@@ -14,6 +15,7 @@ export class FolderFlow extends BaseFlow {
|
||||
* request (see `HttpRequestFlow.create()`), just with a different
|
||||
* context-menu item and a plain naming dialog instead of a rename step.
|
||||
*/
|
||||
@step()
|
||||
async create(parent: Collection | Folder, folder: Folder): Promise<Folder> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const node = await workspace.resolveNode(parent);
|
||||
@@ -44,6 +46,7 @@ export class FolderFlow extends BaseFlow {
|
||||
* Docs) via its "Open in New Tab" context-menu item — not the same as
|
||||
* its "Settings" item, which only opens a rename/move dialog.
|
||||
*/
|
||||
@step()
|
||||
async open(folder: Folder): Promise<FolderPage> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const node = await workspace.findItemNode(
|
||||
@@ -65,6 +68,7 @@ export class FolderFlow extends BaseFlow {
|
||||
* token exists, "Refresh Token" once one does), then reads back the
|
||||
* resulting Refresh/Identity/Access Token fields in one call.
|
||||
*/
|
||||
@step()
|
||||
async fetchOAuth2Tokens(folder: Folder): Promise<OAuth2Tokens> {
|
||||
const folderPage = await this.open(folder);
|
||||
await folderPage.auth.fetchOAuth2Tokens();
|
||||
@@ -76,6 +80,7 @@ export class FolderFlow extends BaseFlow {
|
||||
* scripts (see `ScriptTabComponent.setScripts()`) — these run for
|
||||
* every descendant request before/after that request's own script.
|
||||
*/
|
||||
@step()
|
||||
async setScripts(folder: Folder, scripts: RequestScripts): Promise<void> {
|
||||
const folderPage = await this.open(folder);
|
||||
await folderPage.scripts.setScripts(scripts);
|
||||
@@ -86,6 +91,7 @@ export class FolderFlow extends BaseFlow {
|
||||
* override the collection's base and sub-environment values for every
|
||||
* descendant request.
|
||||
*/
|
||||
@step()
|
||||
async setEnvironmentVariables(folder: Folder, variables: Record<string, string>): Promise<void> {
|
||||
const folderPage = await this.open(folder);
|
||||
await folderPage.setEnvironmentVariables(variables);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { GitChangeList } from "../enums/git-change-list";
|
||||
import { GitDiffView } from "../enums/git-diff-view";
|
||||
import { step } from "../misc/step";
|
||||
import { Commit } from "../models/commit";
|
||||
import { BaseFlow } from "./base.flow";
|
||||
|
||||
@@ -7,6 +8,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
/**
|
||||
* Checks out `branch` directly from the Git Sync dropdown's branch list.
|
||||
*/
|
||||
@step()
|
||||
async switchBranch(branch: string): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.switchBranch(branch);
|
||||
@@ -16,6 +18,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Stages all changes, commits with `message` (no push), and reads back
|
||||
* the resulting commit's id from History.
|
||||
*/
|
||||
@step()
|
||||
async commit(message: string): Promise<Commit> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openCommitDialog();
|
||||
@@ -37,6 +40,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Stages all changes, commits with `message`, then pushes, and reads
|
||||
* back the resulting commit's id from History.
|
||||
*/
|
||||
@step()
|
||||
async commitAndPush(message: string): Promise<Commit> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openCommitDialog();
|
||||
@@ -58,6 +62,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Reads the file names currently listed as staged and unstaged in the
|
||||
* Commit modal, then closes it.
|
||||
*/
|
||||
@step()
|
||||
async getChanges(): Promise<{ staged: string[]; unstaged: string[] }> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openCommitDialog();
|
||||
@@ -71,6 +76,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Stages each of `fileNames` individually from the Commit modal, then
|
||||
* closes it.
|
||||
*/
|
||||
@step()
|
||||
async stageChanges(fileNames: string[]): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openCommitDialog();
|
||||
@@ -84,6 +90,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Unstages each of `fileNames` individually from the Commit modal, then
|
||||
* closes it.
|
||||
*/
|
||||
@step()
|
||||
async unstageChanges(fileNames: string[]): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openCommitDialog();
|
||||
@@ -98,6 +105,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* closes the Commit modal if it's still open, and reads the resulting
|
||||
* commit back from History.
|
||||
*/
|
||||
@step()
|
||||
async commitStaged(message: string): Promise<Commit> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openCommitDialog();
|
||||
@@ -122,6 +130,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
/**
|
||||
* Discards every uncommitted local change.
|
||||
*/
|
||||
@step()
|
||||
async discardAllChanges(): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openCommitDialog();
|
||||
@@ -132,6 +141,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Creates `branch` from the current branch, which switches to it
|
||||
* immediately.
|
||||
*/
|
||||
@step()
|
||||
async createBranch(branch: string): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openBranchesDialog();
|
||||
@@ -143,6 +153,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Attempts to create `branch` from the Branches modal and reads back the
|
||||
* validation error the app shows instead, then closes the modal.
|
||||
*/
|
||||
@step()
|
||||
async createBranchExpectingError(branch: string): Promise<string> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openBranchesDialog();
|
||||
@@ -156,6 +167,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Reads the local and remote-only branch lists from the Branches modal,
|
||||
* then closes it.
|
||||
*/
|
||||
@step()
|
||||
async getBranches(): Promise<{ local: string[]; remote: string[] }> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openBranchesDialog();
|
||||
@@ -169,6 +181,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Fetches `branch` (a branch that exists only on the remote) and checks
|
||||
* it out via the Branches modal.
|
||||
*/
|
||||
@step()
|
||||
async checkoutRemoteBranch(branch: string): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openBranchesDialog();
|
||||
@@ -179,6 +192,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
/**
|
||||
* Fetches from the remote via the Git Sync dropdown.
|
||||
*/
|
||||
@step()
|
||||
async fetch(): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.fetch();
|
||||
@@ -187,6 +201,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
/**
|
||||
* Pulls the current branch from the remote via the Git Sync dropdown.
|
||||
*/
|
||||
@step()
|
||||
async pull(): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.pull();
|
||||
@@ -196,6 +211,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Pushes the current branch's commits to the remote via the Git Sync
|
||||
* dropdown's standalone "Push" action.
|
||||
*/
|
||||
@step()
|
||||
async push(): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.push();
|
||||
@@ -204,6 +220,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
/**
|
||||
* Merges `branch` into whichever branch is currently checked out.
|
||||
*/
|
||||
@step()
|
||||
async mergeBranch(branch: string): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openBranchesDialog();
|
||||
@@ -215,6 +232,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Merges `branch` into the current branch, expecting conflicts, and
|
||||
* resolves all of them by taking the same side.
|
||||
*/
|
||||
@step()
|
||||
async mergeBranchResolvingConflicts(
|
||||
branch: string,
|
||||
version: "Current" | "Incoming",
|
||||
@@ -230,6 +248,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Checks out `switchToBranch` (since a branch can't be deleted while
|
||||
* checked out) and deletes `branch`.
|
||||
*/
|
||||
@step()
|
||||
async deleteBranch(branch: string, switchToBranch = "master"): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openBranchesDialog();
|
||||
@@ -243,6 +262,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* clicking Commit, continues past the "Unrecognized Fields" confirmation
|
||||
* before reading the resulting commit back from History.
|
||||
*/
|
||||
@step()
|
||||
async commitStagedConfirmingUnknownFields(message: string): Promise<Commit> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openCommitDialog();
|
||||
@@ -270,6 +290,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* waiting for the file to leave the unstaged list (so it also works for a
|
||||
* change that comes straight back), then closes the modal.
|
||||
*/
|
||||
@step()
|
||||
async discardChangeAfterReview(fileName: string): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await gitSyncPage.openCommitDialog();
|
||||
@@ -283,6 +304,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Opens the Commit modal (if it isn't already open), selects a file in
|
||||
* the given list and switches its diff pane to the Visual (Beta) view.
|
||||
*/
|
||||
@step()
|
||||
async openVisualDiff(list: GitChangeList, fileName: string): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
if (!(await gitSyncPage.isCommitDialogOpen())) {
|
||||
@@ -295,6 +317,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
/**
|
||||
* Stages one entity from its card in the Visual view of an unstaged file.
|
||||
*/
|
||||
@step()
|
||||
async stageEntity(fileName: string, entityName: string, typeLabel?: string): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await this.openVisualDiff(GitChangeList.Unstaged, fileName);
|
||||
@@ -304,6 +327,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
/**
|
||||
* Unstages one entity from its card in the Visual view of a staged file.
|
||||
*/
|
||||
@step()
|
||||
async unstageEntity(fileName: string, entityName: string, typeLabel?: string): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await this.openVisualDiff(GitChangeList.Staged, fileName);
|
||||
@@ -314,6 +338,7 @@ export class GitSyncFlow extends BaseFlow {
|
||||
* Discards one entity from its card in the Visual view of an unstaged
|
||||
* file, confirming the prompt.
|
||||
*/
|
||||
@step()
|
||||
async discardEntity(fileName: string, entityName: string, typeLabel?: string): Promise<void> {
|
||||
const { gitSyncPage } = this.pageManager;
|
||||
await this.openVisualDiff(GitChangeList.Unstaged, fileName);
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { RequestAuthentication } from "insomnia-data";
|
||||
|
||||
import { ContextMenuItem } from "../enums/context-menu-items";
|
||||
import { TreeNodeType } from "../enums/tree-node-types";
|
||||
import { step } from "../misc/step";
|
||||
import type { Collection } from "../models/collection";
|
||||
import type { Folder } from "../models/folder";
|
||||
import { GraphQLRequest } from "../models/graphql-request";
|
||||
@@ -15,6 +16,7 @@ export class GraphQLRequestFlow extends BaseFlow {
|
||||
* Creates a GraphQL request under `parent` via the standalone project
|
||||
* tree, then verifies it was created.
|
||||
*/
|
||||
@step()
|
||||
async create(
|
||||
parent: Collection | Folder,
|
||||
request: GraphQLRequest,
|
||||
@@ -41,6 +43,7 @@ export class GraphQLRequestFlow extends BaseFlow {
|
||||
* Reads back an existing GraphQL request's fields via the standalone
|
||||
* project tree.
|
||||
*/
|
||||
@step()
|
||||
async get(
|
||||
item: string | { name: string; id?: string },
|
||||
parent?: Collection,
|
||||
@@ -63,6 +66,7 @@ export class GraphQLRequestFlow extends BaseFlow {
|
||||
* Opens `request`, clicks its Auth tab's token-fetch button and reads
|
||||
* the resulting tokens back.
|
||||
*/
|
||||
@step()
|
||||
async fetchOAuth2Tokens(request: GraphQLRequest): Promise<OAuth2Tokens> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const page = this.pageManager.graphqlRequestPage;
|
||||
@@ -78,6 +82,7 @@ export class GraphQLRequestFlow extends BaseFlow {
|
||||
/**
|
||||
* Opens `request` and applies `authentication` on its Auth tab.
|
||||
*/
|
||||
@step()
|
||||
async setAuthentication(
|
||||
request: GraphQLRequest,
|
||||
authentication: RequestAuthentication,
|
||||
@@ -95,6 +100,7 @@ export class GraphQLRequestFlow extends BaseFlow {
|
||||
* Sends `request` and reads back the resulting response via the
|
||||
* standalone project tree.
|
||||
*/
|
||||
@step()
|
||||
async send(request: GraphQLRequest): Promise<ResponseView> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const graphqlRequestPage = this.pageManager.graphqlRequestPage;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ContextMenuItem } from "../enums/context-menu-items";
|
||||
import { TreeNodeType } from "../enums/tree-node-types";
|
||||
import { step } from "../misc/step";
|
||||
import type { Collection } from "../models/collection";
|
||||
import type { Folder } from "../models/folder";
|
||||
import { GrpcRequest } from "../models/grpc-request";
|
||||
@@ -17,6 +18,7 @@ export class GrpcRequestFlow extends BaseFlow {
|
||||
* Creates a gRPC request under `parent` via the standalone project tree,
|
||||
* then verifies it was created.
|
||||
*/
|
||||
@step()
|
||||
async create(parent: Collection | Folder, request: GrpcRequest): Promise<GrpcRequest> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const node = await workspace.resolveNode(parent);
|
||||
@@ -40,6 +42,7 @@ export class GrpcRequestFlow extends BaseFlow {
|
||||
* Reads back an existing gRPC request's fields via the standalone
|
||||
* project tree.
|
||||
*/
|
||||
@step()
|
||||
async get(
|
||||
item: string | { name: string; id?: string },
|
||||
parent?: Collection,
|
||||
@@ -63,6 +66,7 @@ export class GrpcRequestFlow extends BaseFlow {
|
||||
* server-streaming call, or an open client/bidi-streaming call — then
|
||||
* reads back the resulting gRPC status.
|
||||
*/
|
||||
@step()
|
||||
async cancel(request: GrpcRequest): Promise<GrpcResponse> {
|
||||
const grpcRequestPage = await this.openRequest(request);
|
||||
await grpcRequestPage.cancel();
|
||||
@@ -74,6 +78,7 @@ export class GrpcRequestFlow extends BaseFlow {
|
||||
* bidi-streaming call by clicking "Commit", then reads back the
|
||||
* resulting gRPC status and message(s).
|
||||
*/
|
||||
@step()
|
||||
async commit(request: GrpcRequest): Promise<GrpcResponse> {
|
||||
const grpcRequestPage = await this.openRequest(request);
|
||||
await grpcRequestPage.commit();
|
||||
@@ -88,6 +93,7 @@ export class GrpcRequestFlow extends BaseFlow {
|
||||
* response arrives until `commit()` closes the client's half of the
|
||||
* stream.
|
||||
*/
|
||||
@step()
|
||||
async send(request: GrpcRequest): Promise<GrpcResponse> {
|
||||
const grpcRequestPage = await this.openRequest(request);
|
||||
await grpcRequestPage.send();
|
||||
@@ -99,6 +105,7 @@ export class GrpcRequestFlow extends BaseFlow {
|
||||
* Unlike `send()`, this does not wait for a response — none arrives
|
||||
* until `commit()` closes the client's half of the stream.
|
||||
*/
|
||||
@step()
|
||||
async start(request: GrpcRequest): Promise<void> {
|
||||
const grpcRequestPage = await this.openRequest(request);
|
||||
await grpcRequestPage.send();
|
||||
@@ -109,6 +116,7 @@ export class GrpcRequestFlow extends BaseFlow {
|
||||
* on disk and in the request pane's loader data, so a following
|
||||
* `send()` can't go out with the previous body.
|
||||
*/
|
||||
@step()
|
||||
async setBody(request: GrpcRequest, body: string): Promise<void> {
|
||||
if (!request.id) {
|
||||
throw new Error(
|
||||
@@ -126,6 +134,7 @@ export class GrpcRequestFlow extends BaseFlow {
|
||||
* bidi-streaming call. Can be called repeatedly with different bodies
|
||||
* before calling `commit()`.
|
||||
*/
|
||||
@step()
|
||||
async streamMessage(request: GrpcRequest, body: string): Promise<void> {
|
||||
if (!request.id) {
|
||||
throw new Error(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ContextMenuItem } from "../enums/context-menu-items";
|
||||
import type { ResponseExportOption } from "../enums/response-export-option";
|
||||
import { TreeNodeType } from "../enums/tree-node-types";
|
||||
import { step } from "../misc/step";
|
||||
import type { Collection } from "../models/collection";
|
||||
import type { Folder } from "../models/folder";
|
||||
import type { RequestAuthentication, RequestParameter } from "../models/http-request";
|
||||
@@ -15,6 +16,7 @@ export class HttpRequestFlow extends BaseFlow {
|
||||
* Creates a new HTTP request under `parent`, naming it via the rename
|
||||
* flow and applying its fields on the request page.
|
||||
*/
|
||||
@step()
|
||||
async create(parent: Collection | Folder, request: HttpRequest): Promise<HttpRequest> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const node = await workspace.resolveNode(parent);
|
||||
@@ -39,6 +41,7 @@ export class HttpRequestFlow extends BaseFlow {
|
||||
* editor. Use after `create()` for requests with many params, where
|
||||
* per-row editing is prone to a stale-row-list save race.
|
||||
*/
|
||||
@step()
|
||||
async setParamsBulk(request: HttpRequest, params: RequestParameter[]): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const httpRequestPage = this.pageManager.httpRequestPage;
|
||||
@@ -53,6 +56,7 @@ export class HttpRequestFlow extends BaseFlow {
|
||||
* Reads back an existing request's fields by name or identity, looking
|
||||
* it up in the standalone project tree.
|
||||
*/
|
||||
@step()
|
||||
async get(
|
||||
item: string | { name: string; id?: string },
|
||||
): Promise<HttpRequest | undefined> {
|
||||
@@ -74,6 +78,7 @@ export class HttpRequestFlow extends BaseFlow {
|
||||
* Sends `request` and returns the resulting response, locating it in the
|
||||
* standalone project tree.
|
||||
*/
|
||||
@step()
|
||||
async send(request: HttpRequest): Promise<ResponseView> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const httpRequestPage = this.pageManager.httpRequestPage;
|
||||
@@ -96,6 +101,7 @@ export class HttpRequestFlow extends BaseFlow {
|
||||
* any token exists, "Refresh Token" once one does), then reads back
|
||||
* the resulting Refresh/Identity/Access Token fields in one call.
|
||||
*/
|
||||
@step()
|
||||
async fetchOAuth2Tokens(request: HttpRequest): Promise<OAuth2Tokens> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const httpRequestPage = this.pageManager.httpRequestPage;
|
||||
@@ -112,6 +118,7 @@ export class HttpRequestFlow extends BaseFlow {
|
||||
* Exports the response currently shown for the last-sent request to
|
||||
* `filePath` via the response pane's "Preview" dropdown.
|
||||
*/
|
||||
@step()
|
||||
async exportResponse(option: ResponseExportOption, filePath: string): Promise<void> {
|
||||
await this.pageManager.responsePage.exportResponse(option, filePath);
|
||||
}
|
||||
@@ -120,6 +127,7 @@ export class HttpRequestFlow extends BaseFlow {
|
||||
* Saves the large response currently shown for the last-sent request to
|
||||
* `filePath` via the response pane's "Save To File" button.
|
||||
*/
|
||||
@step()
|
||||
async saveResponseToFile(filePath: string): Promise<void> {
|
||||
await this.pageManager.responsePage.saveResponseToFile(filePath);
|
||||
}
|
||||
@@ -128,6 +136,7 @@ export class HttpRequestFlow extends BaseFlow {
|
||||
* Reads an export menu item's label and disabled state for the response
|
||||
* currently shown.
|
||||
*/
|
||||
@step()
|
||||
async getResponseExportOptionState(
|
||||
option: ResponseExportOption,
|
||||
): Promise<{ text: string; disabled: boolean }> {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ContextMenuItem } from "../enums/context-menu-items";
|
||||
import { ImportSource } from "../enums/import-sources";
|
||||
import { step } from "../misc/step";
|
||||
import type { Collection } from "../models/collection";
|
||||
import type { CurlCommand } from "../models/curl-command";
|
||||
import type { Project } from "../models/project";
|
||||
@@ -10,6 +11,7 @@ export class ImportFlow extends BaseFlow {
|
||||
* Opens the Import dialog from `project`'s context menu, scans clipboard
|
||||
* content pasted into the import source, then imports it into the project.
|
||||
*/
|
||||
@step()
|
||||
async importClipboard(project: Project, content: string): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const projectNode = (await workspace.getTree())
|
||||
@@ -32,6 +34,7 @@ export class ImportFlow extends BaseFlow {
|
||||
* Opens the Import dialog from `project`'s context menu, scans a raw curl
|
||||
* command, then imports it into the given collection within that project.
|
||||
*/
|
||||
@step()
|
||||
async importCurl(
|
||||
project: Project,
|
||||
collection: Collection,
|
||||
@@ -60,6 +63,7 @@ export class ImportFlow extends BaseFlow {
|
||||
* Opens the Import dialog from `project`'s context menu, scans a local
|
||||
* file, then imports it into the project.
|
||||
*/
|
||||
@step()
|
||||
async importFile(project: Project, filePath: string): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const projectNode = (await workspace.getTree())
|
||||
@@ -82,6 +86,7 @@ export class ImportFlow extends BaseFlow {
|
||||
* Opens the Import dialog from `project`'s context menu, scans an MCP
|
||||
* server URL, then imports it into the project.
|
||||
*/
|
||||
@step()
|
||||
async importMcp(project: Project, url: string): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const projectNode = (await workspace.getTree())
|
||||
@@ -104,6 +109,7 @@ export class ImportFlow extends BaseFlow {
|
||||
* Opens the Import dialog from `project`'s context menu, scans content
|
||||
* fetched from a URL, then imports it into the project.
|
||||
*/
|
||||
@step()
|
||||
async importUrl(project: Project, url: string): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const projectNode = (await workspace.getTree())
|
||||
|
||||
@@ -2,6 +2,7 @@ import { expect } from "@playwright/test";
|
||||
|
||||
import { type McpCapabilityType, McpListAction } from "../enums/mcp-capability";
|
||||
import { DEFAULT_TIMEOUT } from "../misc/constants";
|
||||
import { step } from "../misc/step";
|
||||
import { McpClient } from "../models/mcp-client";
|
||||
import type { Project } from "../models/project";
|
||||
import type { ResponseView } from "../models/response";
|
||||
@@ -27,6 +28,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
* triggering/awaiting side effects the tool call depends on) before
|
||||
* reading the resulting response.
|
||||
*/
|
||||
@step()
|
||||
async callTool(
|
||||
client: McpClient,
|
||||
tool: string,
|
||||
@@ -61,6 +63,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
* Opens the given MCP Client node and clicks its disconnect action,
|
||||
* waiting for the connection to close.
|
||||
*/
|
||||
@step()
|
||||
async disconnect(client: McpClient): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const mcpClientPage = this.pageManager.mcpClientPage;
|
||||
@@ -75,6 +78,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
* Opens `client`, clicks its Auth tab's token-fetch button and reads the
|
||||
* resulting tokens back.
|
||||
*/
|
||||
@step()
|
||||
async fetchOAuth2Tokens(client: McpClient): Promise<OAuth2Tokens> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const mcpClientPage = this.pageManager.mcpClientPage;
|
||||
@@ -90,6 +94,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
/**
|
||||
* Opens the given MCP Client node and clicks its connect action.
|
||||
*/
|
||||
@step()
|
||||
async connect(
|
||||
client: McpClient,
|
||||
options?: { expectConnected?: boolean },
|
||||
@@ -107,6 +112,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
* Creates an MCP Client node under `parent` (delegating the tree creation
|
||||
* to `workspaceFlow.createMcpClient`), then opens it and sets its URL.
|
||||
*/
|
||||
@step()
|
||||
async create(parent: Project, client: McpClient): Promise<McpClient> {
|
||||
await this.workspaceFlow.createMcpClient(parent, client);
|
||||
|
||||
@@ -139,6 +145,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
* Finds the MCP Client node matching `item` in the workspace tree, opens
|
||||
* it, and reads back its configured URL.
|
||||
*/
|
||||
@step()
|
||||
async get(
|
||||
item: string | { name: string; id?: string },
|
||||
): Promise<McpClient | undefined> {
|
||||
@@ -161,6 +168,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
/**
|
||||
* Opens the given MCP Client node and lists the names of its available tools.
|
||||
*/
|
||||
@step()
|
||||
async getTools(client: McpClient): Promise<string[]> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const mcpClientPage = this.pageManager.mcpClientPage;
|
||||
@@ -175,6 +183,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
* Opens the given MCP Client node and reads the error panel of one of its
|
||||
* sidebar capability items, waiting for it to appear.
|
||||
*/
|
||||
@step()
|
||||
async getListError(client: McpClient, type: McpCapabilityType): Promise<McpCapabilityError> {
|
||||
await this.open(client);
|
||||
return this.pageManager.mcpClientPage.getCapabilityError(type);
|
||||
@@ -184,6 +193,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
* Opens the given MCP Client node and collapses or expands one of its
|
||||
* sidebar capability items.
|
||||
*/
|
||||
@step()
|
||||
async toggleCapability(client: McpClient, type: McpCapabilityType): Promise<void> {
|
||||
await this.open(client);
|
||||
await this.pageManager.mcpClientPage.toggleCapability(type);
|
||||
@@ -193,6 +203,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
* Opens the given MCP Client node and re-runs one capability item's list
|
||||
* request via its actions menu, re-deriving its entries and error.
|
||||
*/
|
||||
@step()
|
||||
async refresh(client: McpClient, type: McpCapabilityType): Promise<void> {
|
||||
await this.open(client);
|
||||
await this.pageManager.mcpClientPage.clickCapabilityAction(type, McpListAction.Reload);
|
||||
@@ -203,6 +214,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
* item's list via its actions menu (offered once the server returned a
|
||||
* `nextCursor`).
|
||||
*/
|
||||
@step()
|
||||
async loadMore(client: McpClient, type: McpCapabilityType): Promise<void> {
|
||||
await this.open(client);
|
||||
await this.pageManager.mcpClientPage.clickCapabilityAction(type, McpListAction.LoadMore);
|
||||
@@ -212,6 +224,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
* Opens the given MCP Client node and reads which list action one of its
|
||||
* capability items currently offers.
|
||||
*/
|
||||
@step()
|
||||
async getListAction(client: McpClient, type: McpCapabilityType): Promise<McpListAction> {
|
||||
await this.open(client);
|
||||
return this.pageManager.mcpClientPage.getCapabilityActionName(type);
|
||||
@@ -220,6 +233,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
/**
|
||||
* Opens the given MCP Client node and filters its sidebar primitives by name.
|
||||
*/
|
||||
@step()
|
||||
async filterCapabilities(client: McpClient, text: string): Promise<void> {
|
||||
await this.open(client);
|
||||
await this.pageManager.mcpClientPage.setCapabilityFilter(text);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { expect } from "@playwright/test";
|
||||
|
||||
import { ContextMenuItem } from "../enums/context-menu-items";
|
||||
import { DEFAULT_TIMEOUT } from "../misc/constants";
|
||||
import { step } from "../misc/step";
|
||||
import type { MockRoute, MockRouteHistoryEntry } from "../models/mock-route";
|
||||
import { MockServer } from "../models/mock-server";
|
||||
import type { Project } from "../models/project";
|
||||
@@ -13,6 +14,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
* Creates a self-hosted Mock Server under `parent` through the project's
|
||||
* create menu and the create dialog, then re-reads it from the tree.
|
||||
*/
|
||||
@step()
|
||||
async create(parent: Project, server: MockServer): Promise<MockServer> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const mockServerPage = this.pageManager.mockServerPage;
|
||||
@@ -30,6 +32,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
/**
|
||||
* Finds a Mock Server's tree node by name/id and returns it as a model.
|
||||
*/
|
||||
@step()
|
||||
async get(item: string | { name: string; id?: string }): Promise<MockServer> {
|
||||
const identity = typeof item === "string" ? { name: item } : item;
|
||||
const node = await this.pageManager.workspacePage.findItemNode(identity);
|
||||
@@ -41,6 +44,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
* Duplicates `server` through its context menu's "Duplicate / Move"
|
||||
* dialog.
|
||||
*/
|
||||
@step()
|
||||
async duplicate(
|
||||
server: MockServer,
|
||||
newName: string,
|
||||
@@ -62,6 +66,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
* Exports `server` (as an Insomnia v5 YAML file) through its context
|
||||
* menu's "Export" item, writing it to `path`.
|
||||
*/
|
||||
@step()
|
||||
async export(server: MockServer, path: string): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const exportPage = this.pageManager.exportPage;
|
||||
@@ -75,6 +80,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
/**
|
||||
* Opens `server`'s Mock Server view.
|
||||
*/
|
||||
@step()
|
||||
async open(server: MockServer): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const mockServerPage = this.pageManager.mockServerPage;
|
||||
@@ -89,6 +95,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
* then, if it carries any, configures its response (status code/text,
|
||||
* body type, body and headers).
|
||||
*/
|
||||
@step()
|
||||
async createRoute(server: MockServer, route: MockRoute): Promise<MockRoute> {
|
||||
const mockServerPage = this.pageManager.mockServerPage;
|
||||
|
||||
@@ -105,6 +112,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
* status code, status text, body type, body and headers — then moves
|
||||
* focus off the last edited editor so it pushes to the server.
|
||||
*/
|
||||
@step()
|
||||
async setResponse(route: MockRoute): Promise<void> {
|
||||
const mockServerPage = this.pageManager.mockServerPage;
|
||||
|
||||
@@ -140,6 +148,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
* Opens `route` and edits its path and/or method through its action
|
||||
* menu's "Edit" dialog.
|
||||
*/
|
||||
@step()
|
||||
async editRoute(
|
||||
route: MockRoute,
|
||||
changes: Pick<MockRoute, "path" | "method">,
|
||||
@@ -153,6 +162,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
/**
|
||||
* Deletes `route` through its action menu and the confirmation dialog.
|
||||
*/
|
||||
@step()
|
||||
async deleteRoute(route: MockRoute): Promise<void> {
|
||||
await this.pageManager.mockServerPage.deleteRoute(route.id!);
|
||||
}
|
||||
@@ -160,6 +170,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
/**
|
||||
* Opens `server` and lists the paths of the routes in its sidebar.
|
||||
*/
|
||||
@step()
|
||||
async getRoutePaths(server: MockServer): Promise<string[]> {
|
||||
await this.open(server);
|
||||
return this.pageManager.mockServerPage.getRoutePaths();
|
||||
@@ -168,6 +179,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
/**
|
||||
* Opens `route` and reads the method and path its URL bar shows.
|
||||
*/
|
||||
@step()
|
||||
async getRoute(route: MockRoute): Promise<{ method: string; path: string }> {
|
||||
const mockServerPage = this.pageManager.mockServerPage;
|
||||
|
||||
@@ -179,6 +191,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
* Opens `route` and reads its full URL on the mock server out of the
|
||||
* "Show URL" dialog.
|
||||
*/
|
||||
@step()
|
||||
async getUrl(route: MockRoute): Promise<string> {
|
||||
const mockServerPage = this.pageManager.mockServerPage;
|
||||
|
||||
@@ -190,6 +203,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
* Opens `route`, clicks "Test" and reads the response the mock server
|
||||
* answered with.
|
||||
*/
|
||||
@step()
|
||||
async test(route: MockRoute): Promise<ResponseView> {
|
||||
const mockServerPage = this.pageManager.mockServerPage;
|
||||
const responsePage = this.pageManager.responsePage;
|
||||
@@ -204,6 +218,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
* On the response `request` currently shows, opens the "→ Mock" tab and
|
||||
* creates a new Mock Route on `server` from it.
|
||||
*/
|
||||
@step()
|
||||
async createRouteFromResponse(
|
||||
server: MockServer,
|
||||
path?: string,
|
||||
@@ -222,6 +237,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
* On the response the request currently shows, opens the "→ Mock" tab
|
||||
* and overwrites `route` with it.
|
||||
*/
|
||||
@step()
|
||||
async overwriteRouteFromResponse(
|
||||
server: MockServer,
|
||||
route: MockRoute,
|
||||
@@ -236,6 +252,7 @@ export class MockServerFlow extends BaseFlow {
|
||||
/**
|
||||
* Opens `route` and reads the hits its History tab has logged.
|
||||
*/
|
||||
@step()
|
||||
async getHistory(route: MockRoute): Promise<MockRouteHistoryEntry[]> {
|
||||
const mockServerPage = this.pageManager.mockServerPage;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CollaboratorRole } from "../enums/collaborator-role";
|
||||
import { step } from "../misc/step";
|
||||
import { BaseFlow } from "./base.flow";
|
||||
|
||||
/**
|
||||
@@ -28,6 +29,7 @@ export class OrganizationFlow extends BaseFlow {
|
||||
* doesn't already match, rather than always clicking through the role
|
||||
* menu.
|
||||
*/
|
||||
@step()
|
||||
async invite(...collaborators: Collaborator[]): Promise<void> {
|
||||
const { invitePage } = this.pageManager;
|
||||
const alreadyListed = (c: Collaborator) =>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { expect } from "@playwright/test";
|
||||
|
||||
import type { ScriptSandboxRuleGroup } from "../enums/script-sandbox-rule-group";
|
||||
import { DEFAULT_TIMEOUT } from "../misc/constants";
|
||||
import { step } from "../misc/step";
|
||||
import type { GitCredential } from "../models/git-credential";
|
||||
import type { AiUrlBackendSettings} from "../models/settings";
|
||||
import { Settings } from "../models/settings";
|
||||
@@ -14,6 +15,7 @@ export class PreferencesFlow extends BaseFlow {
|
||||
* closes it. Only settings that are defined on `settings` are applied
|
||||
* (e.g. `validateSSL` is skipped when undefined).
|
||||
*/
|
||||
@step()
|
||||
async set(settings: Settings): Promise<Settings> {
|
||||
const preferencesPage = this.pageManager.preferencesPage;
|
||||
let aiUrlBackendResult: AiUrlBackendSettings | undefined;
|
||||
@@ -167,6 +169,7 @@ export class PreferencesFlow extends BaseFlow {
|
||||
* `.rejects.toThrow(...)`. If accepted, the modal closes on its own and
|
||||
* this closes Preferences to finish.
|
||||
*/
|
||||
@step()
|
||||
async createPlugin(name: string): Promise<void> {
|
||||
const preferencesPage = this.pageManager.preferencesPage;
|
||||
await preferencesPage.open();
|
||||
@@ -187,6 +190,7 @@ export class PreferencesFlow extends BaseFlow {
|
||||
* Opens Preferences -> Scripting and toggles a single rule group's
|
||||
* sandbox switch, then closes Preferences.
|
||||
*/
|
||||
@step()
|
||||
async toggleScriptSandboxRule(
|
||||
group: ScriptSandboxRuleGroup,
|
||||
enabled: boolean,
|
||||
@@ -203,6 +207,7 @@ export class PreferencesFlow extends BaseFlow {
|
||||
* Credentials, so it's available to select when cloning/connecting a Git
|
||||
* Sync project.
|
||||
*/
|
||||
@step()
|
||||
async addGitCredential(credential: GitCredential): Promise<void> {
|
||||
const { preferencesPage } = this.pageManager;
|
||||
await preferencesPage.open();
|
||||
@@ -217,6 +222,7 @@ export class PreferencesFlow extends BaseFlow {
|
||||
* Preferences. Does NOT include bundled plugins (see
|
||||
* `PreferencesPage.getBundlePlugins()`), which never appear on this tab.
|
||||
*/
|
||||
@step()
|
||||
async getInstalledPlugins(): Promise<string[]> {
|
||||
const { preferencesPage } = this.pageManager;
|
||||
await preferencesPage.open();
|
||||
@@ -232,6 +238,7 @@ export class PreferencesFlow extends BaseFlow {
|
||||
* salt exists yet for this session (a fresh session, or one whose vault
|
||||
* state was just reset — see `resetVaultKey()`).
|
||||
*/
|
||||
@step()
|
||||
async generateVaultKey(): Promise<string> {
|
||||
const { preferencesPage } = this.pageManager;
|
||||
await preferencesPage.open();
|
||||
@@ -249,6 +256,7 @@ export class PreferencesFlow extends BaseFlow {
|
||||
* there's no dialog to surface via `.rejects.toThrow()`); returns `null`
|
||||
* on success.
|
||||
*/
|
||||
@step()
|
||||
async enterVaultKey(key: string): Promise<string | null> {
|
||||
const { preferencesPage } = this.pageManager;
|
||||
await preferencesPage.open();
|
||||
@@ -267,6 +275,7 @@ export class PreferencesFlow extends BaseFlow {
|
||||
* Preferences. Requires a vault salt to already exist for this session
|
||||
* (e.g. via the `vaultSalt` fixture override).
|
||||
*/
|
||||
@step()
|
||||
async resetVaultKey(): Promise<string> {
|
||||
const { preferencesPage } = this.pageManager;
|
||||
await preferencesPage.open();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ContextMenuItem } from "../enums/context-menu-items";
|
||||
import { TreeNodeType } from "../enums/tree-node-types";
|
||||
import { DEFAULT_TIMEOUT } from "../misc/constants";
|
||||
import { step } from "../misc/step";
|
||||
import type { Collection } from "../models/collection";
|
||||
import type { Folder } from "../models/folder";
|
||||
import type { ResponseView } from "../models/response";
|
||||
@@ -13,6 +14,7 @@ export class SocketIORequestFlow extends BaseFlow {
|
||||
* Opens the Socket.IO connection for `request`, locating it via the
|
||||
* standalone project tree before connecting.
|
||||
*/
|
||||
@step()
|
||||
async connect(request: SocketIORequest): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const socketIoRequestPage = this.pageManager.socketIoRequestPage;
|
||||
@@ -27,6 +29,7 @@ export class SocketIORequestFlow extends BaseFlow {
|
||||
* Creates a new Socket.IO request under `parent`, naming it via the
|
||||
* rename flow and applying its fields on the request page.
|
||||
*/
|
||||
@step()
|
||||
async create(
|
||||
parent: Collection | Folder,
|
||||
request: SocketIORequest,
|
||||
@@ -55,6 +58,7 @@ export class SocketIORequestFlow extends BaseFlow {
|
||||
* returns the resulting response, locating it via the standalone project
|
||||
* tree.
|
||||
*/
|
||||
@step()
|
||||
async disconnect(
|
||||
request: SocketIORequest,
|
||||
callback: () => Promise<void> | void = () => {},
|
||||
@@ -79,6 +83,7 @@ export class SocketIORequestFlow extends BaseFlow {
|
||||
* Reads back an existing request's fields by name or identity via the
|
||||
* standalone project tree.
|
||||
*/
|
||||
@step()
|
||||
async get(
|
||||
item: string | { name: string; id?: string },
|
||||
parent?: Collection,
|
||||
@@ -102,6 +107,7 @@ export class SocketIORequestFlow extends BaseFlow {
|
||||
* connection and returns the resulting response, locating it via the
|
||||
* standalone project tree.
|
||||
*/
|
||||
@step()
|
||||
async sendMessage(request: SocketIORequest): Promise<ResponseView> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const socketIoRequestPage = this.pageManager.socketIoRequestPage;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { TreeNodeType } from "../enums/tree-node-types";
|
||||
import { step } from "../misc/step";
|
||||
import type { HttpRequest } from "../models/http-request";
|
||||
import type { ResponseView } from "../models/response";
|
||||
import type { TemplateTag, TemplateTagName } from "../models/template-tag";
|
||||
@@ -16,6 +17,7 @@ export class TemplateTagFlow extends BaseFlow {
|
||||
* arguments too, since those have dedicated getters), and confirms via
|
||||
* "Done".
|
||||
*/
|
||||
@step()
|
||||
async get(template: string): Promise<TemplateTag> {
|
||||
const templateTag = this.pageManager.templateTagPage;
|
||||
const tag = template.match(/\{%.*?%\}/)?.[0] ?? template;
|
||||
@@ -47,6 +49,7 @@ export class TemplateTagFlow extends BaseFlow {
|
||||
* `PreferencesFlow.addCloudCredentials()`). No other tag is supported
|
||||
* today, since these are the only two with dedicated argument setters.
|
||||
*/
|
||||
@step()
|
||||
async edit(template: string, target: TemplateTag): Promise<TemplateTag> {
|
||||
const templateTag = this.pageManager.templateTagPage;
|
||||
const tag = template.match(/\{%.*?%\}/)?.[0] ?? template;
|
||||
@@ -80,6 +83,7 @@ export class TemplateTagFlow extends BaseFlow {
|
||||
* clicks "Clear" to wipe the value cached under its storage key, and
|
||||
* confirms via "Done".
|
||||
*/
|
||||
@step()
|
||||
async clearCachedPrompt(template: string): Promise<void> {
|
||||
const templateTag = this.pageManager.templateTagPage;
|
||||
const tag = template.match(/\{%.*?%\}/)?.[0] ?? template;
|
||||
@@ -93,6 +97,7 @@ export class TemplateTagFlow extends BaseFlow {
|
||||
* value), answers the resulting prompt dialog, and reads back the
|
||||
* response.
|
||||
*/
|
||||
@step()
|
||||
async sendAnsweringPrompt(
|
||||
request: HttpRequest,
|
||||
label: string,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { step } from "../misc/step";
|
||||
import type { Collection } from "../models/collection";
|
||||
import type {
|
||||
VariableSourceModal,
|
||||
@@ -20,6 +21,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
* tag matching `snippet` to open its editor modal, and reads its Source
|
||||
* row plus Live Preview once the preview settles on `expectedPreview`.
|
||||
*/
|
||||
@step()
|
||||
async inspectVariableModal(
|
||||
snippet: string,
|
||||
expectedPreview: string,
|
||||
@@ -35,6 +37,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
* environment page's editor to open its tag editor modal, and reads its
|
||||
* Source row plus Live Preview once the preview settles.
|
||||
*/
|
||||
@step()
|
||||
async inspectEnvironmentPageVariable(
|
||||
snippet: string,
|
||||
expectedPreview: string,
|
||||
@@ -48,6 +51,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
* Hovers the template tag matching `snippet` in the currently open
|
||||
* environment page's editor until its variable-source tooltip shows.
|
||||
*/
|
||||
@step()
|
||||
async hoverEnvironmentPageVariable(
|
||||
snippet: string,
|
||||
): Promise<{ tooltip: string; chip: string }> {
|
||||
@@ -59,6 +63,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
* template tag matching `snippet` until its variable-source tooltip
|
||||
* shows.
|
||||
*/
|
||||
@step()
|
||||
async hoverVariable(
|
||||
snippet: string,
|
||||
): Promise<{ tooltip: string; chip: string }> {
|
||||
@@ -71,6 +76,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
* Hovers the template tag matching `snippet` inside the open Manage
|
||||
* Environments modal until its variable-source tooltip shows.
|
||||
*/
|
||||
@step()
|
||||
async hoverVariableInModal(
|
||||
snippet: string,
|
||||
scope: VariableSourceScope = "modal",
|
||||
@@ -82,6 +88,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
* Hovers the tag matching `snippet` on the Body tab, then hovers the
|
||||
* tooltip's own content and reports whether it stayed visible.
|
||||
*/
|
||||
@step()
|
||||
async hoverTooltipContent(snippet: string): Promise<boolean> {
|
||||
const page = this.pageManager.variableSourcePage;
|
||||
await page.navigate();
|
||||
@@ -94,6 +101,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
* the tooltip text after each hover (the tooltip swaps rather than
|
||||
* vanishing when the pointer moves between tags).
|
||||
*/
|
||||
@step()
|
||||
async hoverBetweenVariables(
|
||||
first: string,
|
||||
firstExpected: string,
|
||||
@@ -116,6 +124,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
/**
|
||||
* Switches the open tag editor modal's selected variable.
|
||||
*/
|
||||
@step()
|
||||
async selectModalVariable(
|
||||
option: string | { label: string },
|
||||
expectedPreview?: string,
|
||||
@@ -131,6 +140,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
* Types a custom template into the open tag editor modal and reads the
|
||||
* re-derived Source row and Live Preview.
|
||||
*/
|
||||
@step()
|
||||
async setModalTemplate(
|
||||
template: string,
|
||||
expectedPreview?: string,
|
||||
@@ -145,6 +155,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
/**
|
||||
* Clicks the open tag editor modal's Source "Open" button.
|
||||
*/
|
||||
@step()
|
||||
async openSourceFromModal(): Promise<void> {
|
||||
await this.pageManager.variableSourcePage.clickModalSourceOpen();
|
||||
}
|
||||
@@ -152,6 +163,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
/**
|
||||
* Clicks the visible variable-source tooltip's "Open" button.
|
||||
*/
|
||||
@step()
|
||||
async openSourceFromTooltip(): Promise<void> {
|
||||
await this.pageManager.variableSourcePage.clickTooltipOpen();
|
||||
}
|
||||
@@ -159,6 +171,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
/**
|
||||
* Opens `collection` and picks an imported project environment for it.
|
||||
*/
|
||||
@step()
|
||||
async selectProjectEnvironment(
|
||||
collection: Collection,
|
||||
optionName: string,
|
||||
@@ -171,6 +184,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
/**
|
||||
* Opens the collection's Manage Environments modal.
|
||||
*/
|
||||
@step()
|
||||
async openManageEnvironmentsModal(): Promise<void> {
|
||||
await this.pageManager.variableSourcePage.openManageEnvironmentsModal();
|
||||
}
|
||||
@@ -178,6 +192,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
/**
|
||||
* Selects an environment row inside the open Manage Environments modal.
|
||||
*/
|
||||
@step()
|
||||
async selectModalEnvironment(name: string): Promise<void> {
|
||||
await this.pageManager.variableSourcePage.selectModalEnvironment(name);
|
||||
}
|
||||
@@ -185,6 +200,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
/**
|
||||
* Reads where an "Open" action landed.
|
||||
*/
|
||||
@step()
|
||||
async getNavigation(
|
||||
landing: VariableSourceLanding,
|
||||
environmentRow?: string,
|
||||
@@ -213,6 +229,7 @@ export class VariableSourceFlow extends BaseFlow {
|
||||
/**
|
||||
* Waits for the Manage Environments modal to close.
|
||||
*/
|
||||
@step()
|
||||
async waitForManageEnvironmentsModalClosed(): Promise<void> {
|
||||
await this.pageManager.variableSourcePage.waitForManageEnvironmentsModalHidden();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { RequestAuthentication } from "insomnia-data";
|
||||
import { ContextMenuItem } from "../enums/context-menu-items";
|
||||
import { TreeNodeType } from "../enums/tree-node-types";
|
||||
import { DEFAULT_TIMEOUT } from "../misc/constants";
|
||||
import { step } from "../misc/step";
|
||||
import type { Collection } from "../models/collection";
|
||||
import type { Folder } from "../models/folder";
|
||||
import type { ResponseView } from "../models/response";
|
||||
@@ -19,6 +20,7 @@ export class WebSocketRequestFlow extends BaseFlow {
|
||||
* Creates a new WebSocket request under `parent`, naming it via the
|
||||
* rename flow and applying its fields on the request page.
|
||||
*/
|
||||
@step()
|
||||
async create(
|
||||
parent: Collection | Folder,
|
||||
request: WebSocketRequest,
|
||||
@@ -51,6 +53,7 @@ export class WebSocketRequestFlow extends BaseFlow {
|
||||
* returns the resulting response, locating it via the standalone
|
||||
* project tree.
|
||||
*/
|
||||
@step()
|
||||
async disconnect(
|
||||
request: WebSocketRequest,
|
||||
callback: () => Promise<void> | void = () => {},
|
||||
@@ -75,6 +78,7 @@ export class WebSocketRequestFlow extends BaseFlow {
|
||||
* Reads back an existing request's fields by name or identity via the
|
||||
* standalone project tree.
|
||||
*/
|
||||
@step()
|
||||
async get(
|
||||
item: string | { name: string; id?: string },
|
||||
parent?: Collection,
|
||||
@@ -97,6 +101,7 @@ export class WebSocketRequestFlow extends BaseFlow {
|
||||
* Opens the WebSocket connection for `request` and returns the
|
||||
* resulting response, locating it via the standalone project tree.
|
||||
*/
|
||||
@step()
|
||||
async send(
|
||||
request: WebSocketRequest,
|
||||
options?: { expectConnected?: boolean },
|
||||
@@ -119,6 +124,7 @@ export class WebSocketRequestFlow extends BaseFlow {
|
||||
/**
|
||||
* Opens `request` and applies `authentication` on its Auth tab.
|
||||
*/
|
||||
@step()
|
||||
async setAuthentication(
|
||||
request: WebSocketRequest,
|
||||
authentication: RequestAuthentication,
|
||||
@@ -138,6 +144,7 @@ export class WebSocketRequestFlow extends BaseFlow {
|
||||
* `timeout` ms elapses), and returns the resulting response, locating it
|
||||
* via the standalone project tree.
|
||||
*/
|
||||
@step()
|
||||
async sendMessage(
|
||||
request: WebSocketRequest,
|
||||
body: WebSocketRequestBody,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { LintSeverity } from '../enums/lint-severity';
|
||||
import { ProjectType } from '../enums/project-types';
|
||||
import { TreeNodeType } from '../enums/tree-node-types';
|
||||
import { DEFAULT_TIMEOUT } from '../misc/constants';
|
||||
import { step } from "../misc/step";
|
||||
import { Collection, TestSuite, UnitTest } from '../models/collection';
|
||||
import { Environment } from '../models/environment';
|
||||
import type { EventStreamRequest } from '../models/event-stream-request';
|
||||
@@ -102,6 +103,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* already a repo) — instead of cloning from a remote URL: the "Open
|
||||
* local folder" mode of the create-project dialog's Git sub-form.
|
||||
*/
|
||||
@step()
|
||||
async openLocalGitFolder(item: Project): Promise<Project> {
|
||||
if (item.folderPath === undefined) {
|
||||
throw new Error(`openLocalGitFolder(): Project "${item.name}" has no folderPath to open`);
|
||||
@@ -123,6 +125,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Preferences -> Credentials) rather than relying on whichever
|
||||
* credential the form defaults to.
|
||||
*/
|
||||
@step()
|
||||
async cloneGitProject(
|
||||
item: Project,
|
||||
credentialName: string,
|
||||
@@ -213,6 +216,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* naming dialog. Authors `collection.spec` into the spec editor
|
||||
* afterward, if given.
|
||||
*/
|
||||
@step()
|
||||
async createInEmptyState(parent: Project, collection: Collection): Promise<Collection> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const node = await workspace.resolveNode(parent);
|
||||
@@ -231,6 +235,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* an action decorated with `@throwOnDialog`) by clicking its "Ok"
|
||||
* button, so a test can continue with further steps afterward.
|
||||
*/
|
||||
@step()
|
||||
async closeDialog(): Promise<void> {
|
||||
await this.pageManager.workspacePage.closeDialog();
|
||||
}
|
||||
@@ -239,6 +244,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Reads whether the one-time "Welcome to focus mode" onboarding popover
|
||||
* is currently shown, then dismisses it if so.
|
||||
*/
|
||||
@step()
|
||||
async dismissFocusModePrompt(): Promise<boolean> {
|
||||
const { workspacePage } = this.pageManager;
|
||||
const shown = await workspacePage.isFocusModePromptShown();
|
||||
@@ -252,6 +258,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* interaction via `pageManager.projectSettingsPage` (e.g. reading the
|
||||
* current git repo path, relocating it).
|
||||
*/
|
||||
@step()
|
||||
async openSettings(project: Project): Promise<void> {
|
||||
const { workspacePage, projectSettingsPage } = this.pageManager;
|
||||
const node = await workspacePage.resolveNode(project);
|
||||
@@ -265,6 +272,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Project has no "Rename" context-menu item, so this is the only way
|
||||
* to rename one.
|
||||
*/
|
||||
@step()
|
||||
async renameProject(project: Project, newName: string): Promise<Project> {
|
||||
const { projectSettingsPage } = this.pageManager;
|
||||
await this.openSettings(project);
|
||||
@@ -279,6 +287,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Right-clicks the tree node matching `item` and deletes it via the
|
||||
* context menu. Does nothing if no matching node is found.
|
||||
*/
|
||||
@step()
|
||||
async delete(
|
||||
item:
|
||||
| Project
|
||||
@@ -304,6 +313,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Opens the command palette, searches for `query`, and reads back the
|
||||
* matching result names.
|
||||
*/
|
||||
@step()
|
||||
async search(query: string): Promise<string[]> {
|
||||
const { commandPalettePage } = this.pageManager;
|
||||
await commandPalettePage.open();
|
||||
@@ -316,6 +326,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* generated code snippet via "Generate Code" — available for regular
|
||||
* requests, not gRPC.
|
||||
*/
|
||||
@step()
|
||||
async generateCode(
|
||||
request: HttpRequest | GraphQLRequest | EventStreamRequest | SocketIORequest | WebSocketRequest,
|
||||
options?: { target?: string; client?: string },
|
||||
@@ -335,6 +346,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* optionally moving the copy into `targetProject`, then re-fetches it as
|
||||
* its full model.
|
||||
*/
|
||||
@step()
|
||||
async duplicateCollection(item: Collection, newName: string, targetProject?: Project): Promise<Collection> {
|
||||
await this.duplicateViaContextMenu(item, newName, targetProject);
|
||||
return this.assertCreated(await this.getCollection(newName), newName);
|
||||
@@ -345,6 +357,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* optionally moving the copy into `targetProject`, then re-fetches it as
|
||||
* its full model.
|
||||
*/
|
||||
@step()
|
||||
async duplicateMcpClient(item: McpClient, newName: string, targetProject?: Project): Promise<McpClient> {
|
||||
await this.duplicateViaContextMenu(item, newName, targetProject);
|
||||
return this.assertCreated(await this.getMcpClient(newName), newName);
|
||||
@@ -355,6 +368,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* optionally moving the copy into `targetProject`, then re-fetches it as
|
||||
* its full model.
|
||||
*/
|
||||
@step()
|
||||
async duplicateEnvironment(item: Environment, newName: string, targetProject?: Project): Promise<Environment> {
|
||||
await this.duplicateViaContextMenu(item, newName, targetProject);
|
||||
|
||||
@@ -374,6 +388,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
/**
|
||||
* Duplicates a request in place via its dedicated "Duplicate" dialog.
|
||||
*/
|
||||
@step()
|
||||
async duplicateRequest(item: RequestItem, newName: string): Promise<{ id: string; name: string }> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const node = await workspace.resolveNode(item);
|
||||
@@ -405,6 +420,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Right-clicks the tree node matching `request` and pins it via the
|
||||
* context menu's "Pin" action.
|
||||
*/
|
||||
@step()
|
||||
async pin(
|
||||
request: HttpRequest | GraphQLRequest | GrpcRequest | EventStreamRequest | SocketIORequest | WebSocketRequest,
|
||||
): Promise<void> {
|
||||
@@ -415,6 +431,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Right-clicks the tree node matching `request` and unpins it via the
|
||||
* context menu's "Unpin" action.
|
||||
*/
|
||||
@step()
|
||||
async unpin(
|
||||
request: HttpRequest | GraphQLRequest | GrpcRequest | EventStreamRequest | SocketIORequest | WebSocketRequest,
|
||||
): Promise<void> {
|
||||
@@ -438,6 +455,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Renames a Collection via its context menu's "Rename" action, then
|
||||
* re-fetches it as its full model under the new name.
|
||||
*/
|
||||
@step()
|
||||
async renameCollection(item: Collection, newName: string): Promise<Collection> {
|
||||
await this.renameViaContextMenu(item, newName);
|
||||
return this.assertCreated(await this.getCollection(newName), newName);
|
||||
@@ -447,6 +465,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Renames an McpClient via its context menu's "Rename" action, then
|
||||
* re-fetches it as its full model under the new name.
|
||||
*/
|
||||
@step()
|
||||
async renameMcpClient(item: McpClient, newName: string): Promise<McpClient> {
|
||||
await this.renameViaContextMenu(item, newName);
|
||||
return this.assertCreated(await this.getMcpClient(newName), newName);
|
||||
@@ -456,6 +475,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Renames an Environment via its context menu's "Rename" action, then
|
||||
* re-fetches it as its full model under the new name.
|
||||
*/
|
||||
@step()
|
||||
async renameEnvironment(item: Environment, newName: string): Promise<Environment> {
|
||||
await this.renameViaContextMenu(item, newName);
|
||||
return this.assertCreated(await this.getEnvironment(newName), newName);
|
||||
@@ -464,6 +484,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
/**
|
||||
* Renames a request in place via its context menu's "Rename" action.
|
||||
*/
|
||||
@step()
|
||||
async renameRequest(item: RequestItem, newName: string): Promise<{ id: string; name: string }> {
|
||||
await this.renameViaContextMenu(item, newName);
|
||||
const newNode = await this.pageManager.workspacePage.findNode(newName, TreeNodeType.Request);
|
||||
@@ -492,6 +513,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* with the Runner (e.g. Cancel/Skip) while it's in flight, rather than
|
||||
* waiting for it to finish.
|
||||
*/
|
||||
@step()
|
||||
async openRunner(collection: Collection, options?: RunnerOptions): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const runner = this.pageManager.runnerPage;
|
||||
@@ -517,6 +539,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* request before returning the collected test-result count and
|
||||
* per-iteration results.
|
||||
*/
|
||||
@step()
|
||||
async run(collection: Collection, options?: RunnerOptions): Promise<RunnerRunResult> {
|
||||
const runner = this.pageManager.runnerPage;
|
||||
await this.openRunner(collection, options);
|
||||
@@ -580,6 +603,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* doesn't carry a spec; use `getCollectionSpec()` instead for one that
|
||||
* does (or might).
|
||||
*/
|
||||
@step()
|
||||
async getCollection(item: string | { name: string; id?: string }): Promise<Collection | undefined> {
|
||||
const identity = typeof item === 'string' ? { name: item } : item;
|
||||
const node = await this.pageManager.workspacePage.findItemNode(identity);
|
||||
@@ -596,6 +620,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* call `getLintState()` separately — it polls for the lint pass to
|
||||
* settle, which not every caller needs.
|
||||
*/
|
||||
@step()
|
||||
async getCollectionSpec(item: string | { name: string; id?: string }): Promise<Collection | undefined> {
|
||||
const identity = typeof item === 'string' ? { name: item } : item;
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
@@ -622,6 +647,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* creation), this can be called at any later point — e.g. filling in a
|
||||
* collection that started out with no spec.
|
||||
*/
|
||||
@step()
|
||||
async fillSpecification(item: Collection, spec: NonNullable<Collection['spec']>): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const node = await workspace.resolveNode(item);
|
||||
@@ -646,6 +672,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* renames it — the two nearly always happen together, since a fresh
|
||||
* suite is always named "New Suite".
|
||||
*/
|
||||
@step()
|
||||
async createTestSuite(item: string | { name: string; id?: string }, name: string): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
await this.openTests(item);
|
||||
@@ -659,6 +686,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* the two nearly always happen together, since a fresh test is always
|
||||
* named "Returns 200".
|
||||
*/
|
||||
@step()
|
||||
async createUnitTest(testSuite: TestSuite, testName: string): Promise<void> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
await this.openTests(testSuite.collection);
|
||||
@@ -672,6 +700,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Opens `item`'s Tests tab, then deletes `suiteName`'s test suite via its
|
||||
* confirm modal (`WorkspacePage.deleteTestSuite()`).
|
||||
*/
|
||||
@step()
|
||||
async deleteTestSuite(item: string | { name: string; id?: string }, suiteName: string): Promise<void> {
|
||||
await this.openTests(item);
|
||||
await this.pageManager.workspacePage.deleteTestSuite(suiteName);
|
||||
@@ -688,6 +717,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* already on the collection's page after `create()`/`getCollectionSpec()`/
|
||||
* `WorkspacePage.setSpecification()`.
|
||||
*/
|
||||
@step()
|
||||
async getLintState(minLintErrors = 0): Promise<NonNullable<Collection['lint']>> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
|
||||
@@ -729,6 +759,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* (`WorkspacePage.getUnitTestNames()`) and a reference back to `item`,
|
||||
* so the suite can be traced back to where it lives.
|
||||
*/
|
||||
@step()
|
||||
async getTestSuite(item: string | { name: string; id?: string }, testSuiteName: string): Promise<TestSuite> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const node = await this.openTests(item);
|
||||
@@ -746,6 +777,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Opens `item`'s Tests tab and reads back every suite name currently
|
||||
* listed in the Test Suites sidebar (`WorkspacePage.getTestSuiteNames()`).
|
||||
*/
|
||||
@step()
|
||||
async getTestSuiteNames(item: string | { name: string; id?: string }): Promise<string[]> {
|
||||
await this.openTests(item);
|
||||
return this.pageManager.workspacePage.getTestSuiteNames();
|
||||
@@ -756,6 +788,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* via its confirm modal (`WorkspacePage.removeRuleset()`), reverting to
|
||||
* the default OAS ruleset.
|
||||
*/
|
||||
@step()
|
||||
async removeRuleset(item: string | { name: string; id?: string }): Promise<void> {
|
||||
const identity = typeof item === 'string' ? { name: item } : item;
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
@@ -770,6 +803,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Opens `testSuite`'s Tests tab, selects `testSuite`, runs every test in
|
||||
* it, and reads back the resulting pass/fail summary and per-test rows.
|
||||
*/
|
||||
@step()
|
||||
async runAllTests(testSuite: TestSuite): Promise<UnitTestRunResult> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
await this.openTests(testSuite.collection);
|
||||
@@ -792,6 +826,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* resulting "Invalid Spectral Ruleset" error dialog and returns
|
||||
* `"invalid"` instead, leaving the default ruleset active.
|
||||
*/
|
||||
@step()
|
||||
async uploadRuleset(filePath: string, expectCode?: string): Promise<string[] | 'invalid'> {
|
||||
const workspace = this.pageManager.workspacePage;
|
||||
const result = await workspace.uploadRuleset(filePath);
|
||||
@@ -859,6 +894,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
/**
|
||||
* Finds the tree node matching `item` and returns it as a Project model.
|
||||
*/
|
||||
@step()
|
||||
async getProject(item: string | { name: string; id?: string }): Promise<Project | undefined> {
|
||||
const identity = typeof item === 'string' ? { name: item } : item;
|
||||
const node = await this.pageManager.workspacePage.findItemNode(identity);
|
||||
@@ -961,6 +997,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* Deletes the tree node matching `item` with the sidebar's Delete shortcut
|
||||
* and confirms the dialog. Does nothing if no matching node is found.
|
||||
*/
|
||||
@step()
|
||||
async deleteViaShortcut(
|
||||
item:
|
||||
| Collection
|
||||
@@ -985,6 +1022,7 @@ export class WorkspaceFlow extends BaseFlow {
|
||||
* reads the "Unknown Fields Detected" warning off the post-scan screen
|
||||
* before confirming the clone.
|
||||
*/
|
||||
@step()
|
||||
async cloneWithScanReport(
|
||||
item: Project,
|
||||
credentialName: string,
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
trackHiddenWindowCoverage,
|
||||
} from "./coverage";
|
||||
import { launchInsomniaElectron, SESSION_ID } from "./launch";
|
||||
import { closeOpenStepGroup } from "./step-instrumentation";
|
||||
|
||||
export * from "./constants";
|
||||
export { launchInsomniaElectron, SESSION_ID };
|
||||
@@ -326,7 +325,6 @@ export const test = base.extend<Fixtures>({
|
||||
await pageManager.workspacePage.navigate();
|
||||
await disableFocusModeDefault(flowManager);
|
||||
await use({ page: window, pageManager, flowManager });
|
||||
await closeOpenStepGroup();
|
||||
|
||||
// A test that called AppFlow.restart() leaves the relaunched app
|
||||
// behind — the `insomnia`/`window` fixtures only know the original
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
import { test } from "@playwright/test";
|
||||
|
||||
const step = test.step.bind(test);
|
||||
|
||||
const EXCLUDED_METHODS = new Set([
|
||||
"setContext",
|
||||
"switchTab",
|
||||
"fillOneLineEditor",
|
||||
"readCodeMirror",
|
||||
"setKeyValuePairs",
|
||||
"readKeyValuePairs",
|
||||
"createProject",
|
||||
"createCollection",
|
||||
"createDocument",
|
||||
"createMcpClient",
|
||||
"createEnvironment",
|
||||
"rows",
|
||||
"fillField",
|
||||
"editCookie",
|
||||
"caCertificateRow",
|
||||
"clientCertificateRow",
|
||||
"fileCard",
|
||||
"dragRow",
|
||||
"invitationRow",
|
||||
"oAuth1FieldEditor",
|
||||
"oauth2FieldEditor",
|
||||
"oauth2TokenInput",
|
||||
"projectTypeTile",
|
||||
"isToggleOn",
|
||||
"setToggle",
|
||||
"tab",
|
||||
"openAddTabMenu",
|
||||
"toAiUrlBackendSettings",
|
||||
]);
|
||||
|
||||
const GET_METHOD_PATTERN = /^get[A-Z]/;
|
||||
|
||||
function humanize(name: string): string {
|
||||
const words = name
|
||||
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
||||
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
||||
.split(" ")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(" ");
|
||||
return words
|
||||
.replace(/Graph Ql/g, "GraphQL")
|
||||
.replace(/Socket Io/g, "Socket.IO")
|
||||
.replace(/\bMcp\b/g, "MCP")
|
||||
.replace(/\bGrpc\b/g, "gRPC");
|
||||
}
|
||||
|
||||
const BARE_VERB_PATTERN = /^[a-z]+$/;
|
||||
const CLASS_SUFFIX_PATTERN = /(Flow|Page)$/;
|
||||
|
||||
interface IdentifiedObjectArg {
|
||||
pos: number;
|
||||
identifier: string;
|
||||
typeLabel?: string;
|
||||
}
|
||||
|
||||
function describeArgs(className: string, key: string, args: unknown[]): string {
|
||||
let stringArg: string | undefined;
|
||||
let stringPos = -1;
|
||||
const objectArgs: IdentifiedObjectArg[] = [];
|
||||
|
||||
args.forEach((arg, pos) => {
|
||||
if (typeof arg === "string") {
|
||||
if (stringArg === undefined) {
|
||||
stringArg = arg;
|
||||
stringPos = pos;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!arg || typeof arg !== "object" || Array.isArray(arg)) return;
|
||||
|
||||
const candidate =
|
||||
(arg as { name?: unknown; id?: unknown }).name ??
|
||||
(arg as { name?: unknown; id?: unknown }).id;
|
||||
if (typeof candidate !== "string") return;
|
||||
|
||||
const ctorName = (arg as object).constructor?.name;
|
||||
objectArgs.push({
|
||||
pos,
|
||||
identifier: candidate,
|
||||
typeLabel:
|
||||
ctorName && ctorName !== "Object" ? humanize(ctorName) : undefined,
|
||||
});
|
||||
});
|
||||
|
||||
const preposition = key.toLowerCase().startsWith("create") ? "in" : "to";
|
||||
const last = objectArgs[objectArgs.length - 1];
|
||||
|
||||
const domain = humanize(className.replace(CLASS_SUFFIX_PATTERN, ""));
|
||||
let subject = last?.typeLabel ?? "";
|
||||
if (subject === domain) subject = "";
|
||||
if (!subject && BARE_VERB_PATTERN.test(key) && domain) subject = domain;
|
||||
|
||||
const primitives = [
|
||||
stringArg !== undefined ? { value: stringArg, pos: stringPos } : null,
|
||||
last ? { value: last.identifier, pos: last.pos } : null,
|
||||
]
|
||||
.filter((p): p is { value: string; pos: number } => p !== null)
|
||||
.sort((a, b) => a.pos - b.pos)
|
||||
.map((p) => p.value);
|
||||
let detail = primitives.length ? ` (${primitives.join(", ")})` : "";
|
||||
|
||||
for (const target of objectArgs.slice(0, -1)) {
|
||||
detail += ` ${preposition}${target.typeLabel ? ` ${target.typeLabel}` : ""} (${target.identifier})`;
|
||||
}
|
||||
|
||||
return (subject ? ` ${subject}` : "") + detail;
|
||||
}
|
||||
|
||||
interface QueueItem {
|
||||
childTitle: string;
|
||||
fn: () => unknown;
|
||||
resolve: (v: unknown) => void;
|
||||
reject: (e: unknown) => void;
|
||||
}
|
||||
|
||||
interface PageGroup {
|
||||
instance: object;
|
||||
run: (childTitle: string, fn: () => unknown) => Promise<unknown>;
|
||||
close: () => Promise<void>;
|
||||
}
|
||||
|
||||
let isProcessingGroupItem = false;
|
||||
|
||||
function createPageGroup(instance: object, title: string): PageGroup {
|
||||
const queue: QueueItem[] = [];
|
||||
let wake: (() => void) | null = null;
|
||||
let closed = false;
|
||||
|
||||
const finished = step(title, async () => {
|
||||
for (;;) {
|
||||
const item = queue.shift();
|
||||
if (item) {
|
||||
isProcessingGroupItem = true;
|
||||
try {
|
||||
item.resolve(await step(item.childTitle, item.fn));
|
||||
} catch (e) {
|
||||
item.reject(e);
|
||||
} finally {
|
||||
isProcessingGroupItem = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (closed) return;
|
||||
await new Promise<void>((resolve) => (wake = resolve));
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
instance,
|
||||
run(childTitle, fn) {
|
||||
if (isProcessingGroupItem) return Promise.resolve(step(childTitle, fn));
|
||||
return new Promise((resolve, reject) => {
|
||||
queue.push({ childTitle, fn, resolve, reject });
|
||||
wake?.();
|
||||
wake = null;
|
||||
});
|
||||
},
|
||||
async close() {
|
||||
closed = true;
|
||||
wake?.();
|
||||
wake = null;
|
||||
await finished;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
let currentPageGroup: PageGroup | null = null;
|
||||
|
||||
async function getPageGroup(
|
||||
instance: object,
|
||||
humanClassName: string,
|
||||
): Promise<PageGroup> {
|
||||
if (currentPageGroup?.instance === instance) return currentPageGroup;
|
||||
if (isProcessingGroupItem && currentPageGroup) return currentPageGroup;
|
||||
if (currentPageGroup) {
|
||||
const previous = currentPageGroup;
|
||||
currentPageGroup = null;
|
||||
await previous.close();
|
||||
}
|
||||
const group = createPageGroup(instance, humanClassName);
|
||||
currentPageGroup = group;
|
||||
return group;
|
||||
}
|
||||
|
||||
export async function closeOpenStepGroup(): Promise<void> {
|
||||
if (!currentPageGroup) return;
|
||||
const group = currentPageGroup;
|
||||
currentPageGroup = null;
|
||||
await group.close();
|
||||
}
|
||||
|
||||
export function instrumentWithSteps<T extends object>(instance: T): T {
|
||||
const className = instance.constructor.name;
|
||||
const isPageClass = className.endsWith("Page");
|
||||
const humanClassName = humanize(className);
|
||||
const seen = new Set<string>(["constructor"]);
|
||||
let proto = Object.getPrototypeOf(instance);
|
||||
|
||||
while (proto && proto !== Object.prototype) {
|
||||
for (const key of Object.getOwnPropertyNames(proto)) {
|
||||
if (seen.has(key) || EXCLUDED_METHODS.has(key)) continue;
|
||||
seen.add(key);
|
||||
if (isPageClass && GET_METHOD_PATTERN.test(key)) continue;
|
||||
|
||||
const descriptor = Object.getOwnPropertyDescriptor(proto, key);
|
||||
if (!descriptor || typeof descriptor.value !== "function") continue;
|
||||
|
||||
const original = (instance as any)[key] as (
|
||||
...args: unknown[]
|
||||
) => unknown;
|
||||
const childTitle = (args: unknown[]) =>
|
||||
`${humanize(key)}${describeArgs(className, key, args)}`;
|
||||
|
||||
(instance as any)[key] = isPageClass
|
||||
? async function (...args: unknown[]) {
|
||||
const group = await getPageGroup(instance, humanClassName);
|
||||
return group.run(childTitle(args), () =>
|
||||
original.apply(instance, args),
|
||||
);
|
||||
}
|
||||
: async function (...args: unknown[]) {
|
||||
await closeOpenStepGroup();
|
||||
try {
|
||||
return await step(`${humanClassName}: ${childTitle(args)}`, () =>
|
||||
original.apply(instance, args),
|
||||
);
|
||||
} finally {
|
||||
await closeOpenStepGroup();
|
||||
}
|
||||
};
|
||||
}
|
||||
proto = Object.getPrototypeOf(proto);
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { test } from "@playwright/test";
|
||||
|
||||
function humanize(name: string): string {
|
||||
const words = name
|
||||
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
||||
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2")
|
||||
.split(" ")
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
||||
.join(" ");
|
||||
return words
|
||||
.replace(/Graph Ql/g, "GraphQL")
|
||||
.replace(/Socket Io/g, "Socket.IO")
|
||||
.replace(/\bMcp\b/g, "MCP")
|
||||
.replace(/\bGrpc\b/g, "gRPC");
|
||||
}
|
||||
|
||||
const BARE_VERB_PATTERN = /^[a-z]+$/;
|
||||
const CLASS_SUFFIX_PATTERN = /(Flow|Page)$/;
|
||||
|
||||
interface IdentifiedObjectArg {
|
||||
pos: number;
|
||||
identifier: string;
|
||||
typeLabel?: string;
|
||||
}
|
||||
|
||||
function describeArgs(className: string, key: string, args: unknown[]): string {
|
||||
let stringArg: string | undefined;
|
||||
let stringPos = -1;
|
||||
const objectArgs: IdentifiedObjectArg[] = [];
|
||||
|
||||
args.forEach((arg, pos) => {
|
||||
if (typeof arg === "string") {
|
||||
if (stringArg === undefined) {
|
||||
stringArg = arg;
|
||||
stringPos = pos;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!arg || typeof arg !== "object" || Array.isArray(arg)) return;
|
||||
|
||||
const candidate =
|
||||
(arg as { name?: unknown; id?: unknown }).name ??
|
||||
(arg as { name?: unknown; id?: unknown }).id;
|
||||
if (typeof candidate !== "string") return;
|
||||
|
||||
const ctorName = (arg as object).constructor?.name;
|
||||
objectArgs.push({
|
||||
pos,
|
||||
identifier: candidate,
|
||||
typeLabel:
|
||||
ctorName && ctorName !== "Object" ? humanize(ctorName) : undefined,
|
||||
});
|
||||
});
|
||||
|
||||
const preposition = key.toLowerCase().startsWith("create") ? "in" : "to";
|
||||
const last = objectArgs[objectArgs.length - 1];
|
||||
|
||||
const domain = humanize(className.replace(CLASS_SUFFIX_PATTERN, ""));
|
||||
let subject = last?.typeLabel ?? "";
|
||||
if (subject === domain) subject = "";
|
||||
if (!subject && BARE_VERB_PATTERN.test(key) && domain) subject = domain;
|
||||
|
||||
const primitives = [
|
||||
stringArg !== undefined ? { value: stringArg, pos: stringPos } : null,
|
||||
last ? { value: last.identifier, pos: last.pos } : null,
|
||||
]
|
||||
.filter((p): p is { value: string; pos: number } => p !== null)
|
||||
.sort((a, b) => a.pos - b.pos)
|
||||
.map((p) => p.value);
|
||||
let detail = primitives.length ? ` (${primitives.join(", ")})` : "";
|
||||
|
||||
for (const target of objectArgs.slice(0, -1)) {
|
||||
detail += ` ${preposition}${target.typeLabel ? ` ${target.typeLabel}` : ""} (${target.identifier})`;
|
||||
}
|
||||
|
||||
return (subject ? ` ${subject}` : "") + detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method decorator that reports a Flow method as a `test.step()`.
|
||||
*
|
||||
* Without a `title` the step is named after the method and its arguments
|
||||
* (e.g. `Http Request Flow: Create (Request name)`); pass a `title` to
|
||||
* override that.
|
||||
*/
|
||||
export function step(title?: string) {
|
||||
return function <This extends object, Args extends unknown[], Return>(
|
||||
target: (this: This, ...args: Args) => Promise<Return>,
|
||||
context: ClassMethodDecoratorContext<
|
||||
This,
|
||||
(this: This, ...args: Args) => Promise<Return>
|
||||
>,
|
||||
) {
|
||||
const key = String(context.name);
|
||||
return function (this: This, ...args: Args): Promise<Return> {
|
||||
const className = this.constructor.name;
|
||||
const stepTitle =
|
||||
title ?? `${humanize(key)}${describeArgs(className, key, args)}`;
|
||||
return test.step(`${humanize(className)}: ${stepTitle}`, () =>
|
||||
target.call(this, ...args),
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { ElectronApplication } from "@playwright/test";
|
||||
import type { Page } from "playwright-core";
|
||||
|
||||
import { instrumentWithSteps } from "../misc/step-instrumentation";
|
||||
import type { BasePage } from "./base.page";
|
||||
import { CertificatesPage } from "./certificates.page";
|
||||
import { CloudSyncPage } from "./cloud-sync.page";
|
||||
@@ -282,8 +281,7 @@ export class PageManager {
|
||||
}
|
||||
|
||||
private getOrCreate<T extends object>(key: string, factory: () => T): T {
|
||||
if (!this.instances.has(key))
|
||||
this.instances.set(key, instrumentWithSteps(factory()));
|
||||
if (!this.instances.has(key)) this.instances.set(key, factory());
|
||||
return this.instances.get(key) as T;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ const INSOMNIA_SRC_PACKAGE = path.resolve(import.meta.dirname, "..", "insomnia")
|
||||
ensureCerts();
|
||||
|
||||
// The suite is serial by design: misc/git-fixtures.ts (`currentGitRepo`),
|
||||
// misc/step-instrumentation.ts (`currentPageGroup`) and the mock servers'
|
||||
// module-level state and the mock servers'
|
||||
// shared state all assume one test at a time. Fail fast instead of letting
|
||||
// `--workers=N` produce cross-test interference.
|
||||
const workersIdx = process.argv.findIndex((arg) => arg === "--workers" || arg.startsWith("--workers=") || arg === "-j");
|
||||
|
||||
Reference in new issue
Block a user