Add tests and YAML configurations for environment variable sourcing and response handling

- Introduced new YAML files for global environments, project environment sources, and environment cascade collections.
- Implemented tests for variable source levels, modal editing, tooltip behavior, and response downloads.
- Enhanced pre-request scripts to handle client certificates and proxy updates.
- Verified caching behavior for prompt tags and response tag trigger behaviors.
This commit is contained in:
Bob Qiu authored and GitHub committed 2026-11-03 17:18:49 +08:00
1 parent 641d924b52
commit 73096cbcdc
15 files changed
+1257

No files matched your search

@@ -0,0 +1,72 @@
import { faker } from "@faker-js/faker";
import { expect } from "@playwright/test";
import { ProjectType } from "../../enums/project-types";
import { test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
import { Project } from "../../models/project";
test("Verify the project and collection environment pickers open and close independently", async ({
user,
}) => {
const { workspaceFlow, environmentFlow } = user.flowManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
const collection = await workspaceFlow.createCollection(
project,
new Collection(faker.string.alphanumeric(10)),
);
const openAfterCollection = await environmentFlow.getOpenPickersAfterOpening(collection, "collection");
const openAfterProject = await environmentFlow.getOpenPickersAfterOpening(collection, "project");
expect(openAfterCollection).toEqual(["collection"]);
expect(openAfterProject).toEqual(["project"]);
});
test("Verify Add Project Environment from the picker lists the new environment without selecting it", async ({
user,
}) => {
const { workspaceFlow, environmentFlow } = user.flowManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
const collection = await workspaceFlow.createCollection(
project,
new Collection(faker.string.alphanumeric(10)),
);
const environmentName = faker.string.alphanumeric(10);
const pickerState = await environmentFlow.addProjectEnvironmentFromPicker(collection, environmentName);
expect(pickerState.selected).toContain("No Project Environment");
expect(pickerState.options.some((option) => option.includes(environmentName))).toBe(true);
});
test("Verify Add Sub Environment and Add Private Sub Environment create environments with the correct privacy", async ({
user,
}) => {
const { workspaceFlow, environmentFlow } = user.flowManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
const collection = await workspaceFlow.createCollection(
project,
new Collection(faker.string.alphanumeric(10)),
);
const shared = await environmentFlow.createCollectionSubEnvironment(collection, {
name: faker.string.alphanumeric(10),
});
const privateEnvironment = await environmentFlow.createCollectionSubEnvironment(collection, {
name: faker.string.alphanumeric(10),
isPrivate: true,
});
expect(shared.isPrivate).toBe(false);
expect(privateEnvironment.isPrivate).toBe(true);
});
@@ -0,0 +1,76 @@
import { faker } from "@faker-js/faker";
import { expect } from "@playwright/test";
import { HttpMethod } from "../../enums/http-method";
import { ProjectType } from "../../enums/project-types";
import { HTTP_SERVER, test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
import { EnvironmentKvPairDataType } from "../../models/environment";
import { Folder } from "../../models/folder";
import { Project } from "../../models/project";
const sharedVariable = faker.string.alpha(8);
const subOnlyVariable = faker.string.alpha(9);
test("Verify a folder environment overrides sub and base environments for its requests while other variables fall through", async ({
user,
}) => {
const { workspaceFlow, environmentFlow, folderFlow, httpRequestFlow } =
user.flowManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
const collection = await workspaceFlow.createCollection(
project,
new Collection(faker.string.alphanumeric(10)),
);
const baseShared = faker.string.alphanumeric(10);
const baseSubOnly = faker.string.alphanumeric(10);
const subShared = faker.string.alphanumeric(10);
const subSubOnly = faker.string.alphanumeric(10);
const folderShared = faker.string.alphanumeric(10);
const baseEnvironment = await environmentFlow.createBaseEnvironment(project, {
name: faker.string.alphanumeric(10),
kvPairData: [
{ id: "", name: sharedVariable, value: baseShared, type: EnvironmentKvPairDataType.STRING, enabled: true },
{ id: "", name: subOnlyVariable, value: baseSubOnly, type: EnvironmentKvPairDataType.STRING, enabled: true },
],
});
const subEnvironment = await environmentFlow.createSubEnvironment(baseEnvironment, {
name: faker.string.alphanumeric(10),
kvPairData: [
{ id: "", name: sharedVariable, value: subShared, type: EnvironmentKvPairDataType.STRING, enabled: true },
{ id: "", name: subOnlyVariable, value: subSubOnly, type: EnvironmentKvPairDataType.STRING, enabled: true },
],
});
await environmentFlow.link(collection, subEnvironment);
const folder = await folderFlow.create(collection, new Folder(faker.string.alphanumeric(10)));
await folderFlow.setEnvironmentVariables(folder, { [sharedVariable]: folderShared });
const requestUrl = `${HTTP_SERVER}/post?shared={{ ${sharedVariable} }}&subOnly={{ ${subOnlyVariable} }}`;
const requestInFolder = await httpRequestFlow.create(folder, {
name: faker.string.alphanumeric(10),
method: HttpMethod.Post,
url: requestUrl,
});
const requestOutsideFolder = await httpRequestFlow.create(collection, {
name: faker.string.alphanumeric(10),
method: HttpMethod.Post,
url: requestUrl,
});
const responseInFolder = await httpRequestFlow.send(requestInFolder);
const responseOutsideFolder = await httpRequestFlow.send(requestOutsideFolder);
expect(responseInFolder.statusCode).toBe(200);
expect(responseInFolder.body).toMatchObject({
args: { shared: folderShared, subOnly: subSubOnly },
});
expect(responseOutsideFolder.statusCode).toBe(200);
expect(responseOutsideFolder.body).toMatchObject({
args: { shared: subShared, subOnly: subSubOnly },
});
});
@@ -0,0 +1,106 @@
type: collection.insomnia.rest/5.0
schema_version: "5.1"
name: collection-for-global-environments
meta:
id: wrk_711bb959b80944d793e93df7c237ac56
created: 1736618909373
modified: 1736618909373
collection:
- url: localhost:4060/post
name: New Request
meta:
id: req_180c79263db64fd8a01a2579cdcb5778
created: 1670587797191
modified: 1728549768587
isPrivate: false
method: GET
body:
mimeType: text/plain
text: |-
{{ _.exampleObject.anotherString }}
{{ _.exampleString }}
{{ _.exampleNumber }}
{{ _.exampleObject.anotherNumber }}
{{ _['global-base'] }}
{{ _['global-sub'] }}
headers:
- name: Content-Type
value: text/plain
settings:
renderRequestBody: true
encodeUrl: true
followRedirects: global
cookies:
send: true
store: true
rebuildPath: true
- url: http://localhost:4060/post
name: Global Var Request
meta:
id: req_5f4e6d7c8b9a0f1e2d3c4b5a69788779
created: 1736618909374
modified: 1736618909374
isPrivate: false
method: POST
body:
mimeType: application/json
text: |-
{
"global-base": "{{ _['global-base'] }}"
}
headers:
- name: Content-Type
value: application/json
settings:
renderRequestBody: true
encodeUrl: true
followRedirects: global
cookies:
send: true
store: true
rebuildPath: true
cookieJar:
name: Default Jar
meta:
id: jar_11119dfd3e8649b2a6195f90e0d4133a
created: 1670587793248
modified: 1670587793248
environments:
name: Base Environment
meta:
id: env_6395586a9463467eb7c2b0d2a5a47bea
created: 1670587793246
modified: 1670587811762
isPrivate: false
data:
exampleString: baseenv0
exampleNumber: 1001
exampleObject:
anotherString: baseenv1
anotherNumber: 1001
subEnvironments:
- name: ExampleA
meta:
id: env_0e6c9e63f01e48a1b2d64f269dea4c87
created: 1670587812382
modified: 1670587818649
isPrivate: false
data:
exampleString: subenvA0
exampleNumber: 1111
globalSourcedVar: "{{ _['global-base'] }}"
exampleObject:
anotherString: subenvA1
anotherNumber: 1111
- name: ExampleB
meta:
id: env_bbf062fb34d94777841fb1f2ed60cb7d
created: 1670587826837
modified: 1670587831933
isPrivate: false
data:
exampleString: subenvB0
exampleNumber: 2222
exampleObject:
anotherString: subenvB1
anotherNumber: 2222
@@ -0,0 +1,145 @@
type: collection.insomnia.rest/5.0
schema_version: "5.1"
name: Environment Cascade
meta:
id: wrk_7c1e4a8f9b2d4c6e8f1a3b5c7d9e0f11
created: 1739887154405
modified: 1739887154405
collection:
- url: http://localhost:4060/post
name: cascade request no folder
meta:
id: req_4c6e8a0b2d4f6a8c0e2b4d6f8a0c2e18
created: 1636141014552
modified: 1636707449231
isPrivate: false
sortKey: -1636141014554
method: POST
body:
mimeType: application/json
text: |-
{
"cascadeVar": "{{ _.cascadeVar }}"
}
headers:
- name: Content-Type
value: application/json
settings:
renderRequestBody: true
encodeUrl: true
followRedirects: global
cookies:
send: true
store: true
rebuildPath: true
- name: cascadeFolder
meta:
id: fld_5b2c9e1a7d3f4a6b8c0d2e4f6a8b0c12
created: 1668533312225
modified: 1668533312225
sortKey: -1668533312225
children:
- url: http://localhost:4060/post
name: cascade request
meta:
id: req_1f3e5a7c9b1d3f5a7c9e1b3d5f7a9c13
created: 1636141014552
modified: 1636707449231
isPrivate: false
sortKey: -1636141014553
method: POST
body:
mimeType: application/json
text: |-
{
"cascadeVar": "{{ _.cascadeVar }}",
"subOnlyVar": "{{ _.subOnlyVar }}"
}
headers:
- name: Content-Type
value: application/json
settings:
renderRequestBody: true
encodeUrl: true
followRedirects: global
cookies:
send: true
store: true
rebuildPath: true
- name: innerFolder
meta:
id: fld_9d4e8f0a1b2c3d4e5f6a7b8c9d0e1f22
created: 1668533312226
modified: 1668533312226
sortKey: -1668533312224
children:
- url: http://localhost:4060/post
name: inner request
meta:
id: req_0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c23
created: 1636141014554
modified: 1636707449232
isPrivate: false
sortKey: -1636141014554
method: POST
body:
mimeType: application/json
text: |-
{
"cascadeVar": "{{ _.cascadeVar }}",
"folderOverride": "{{ _.folderOverride }}"
}
headers:
- name: Content-Type
value: application/json
settings:
renderRequestBody: true
encodeUrl: true
followRedirects: global
cookies:
send: true
store: true
rebuildPath: true
environment:
folderOverride: fromInnerFolder
environment:
cascadeVar: fromFolder
folderOverride: fromOuterFolder
cookieJar:
name: Default Jar
meta:
id: jar_8e2f4a6c1b3d5f7a9c1e3b5d7f9a1c14
created: 1707808692807
modified: 1707808692807
environments:
name: Base Environment
meta:
id: env_9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c15
created: 1670587793246
modified: 1670587811762
isPrivate: false
data:
cascadeVar: fromBase
subOnlyVar: subOnly_base
baseOnlyVar: baseOnlyValue
subEnvironments:
- name: SubEnvA
meta:
id: env_2a4c6e8b0d2f4a6c8e0b2d4f6a8c0e16
created: 1670587812382
modified: 1670587818649
isPrivate: false
data:
cascadeVar: fromSubA
subOnlyVar: subOnly_A
baseSourcedVar: '{{ _.baseOnlyVar }}'
folderSourcedVar: '{{ _.folderOverride }}'
- name: SubEnvB
meta:
id: env_3b5d7f9c1e3a5c7e9b1d3f5a7c9e1f17
created: 1670587826837
modified: 1736605621292
isPrivate: false
data:
cascadeVar: fromSubB
subOnlyVar: subOnly_B
@@ -0,0 +1,28 @@
type: environment.insomnia.rest/5.0
schema_version: "5.1"
name: global-environment
meta:
id: wrk_7a6debc2484d48db88897609ab21f0eb
created: 1736618596041
modified: 1736618596041
environments:
name: Base Environment
meta:
id: env_aace0582bc8c4445be6689f340a08c20
created: 1728549301585
modified: 1728549418314
isPrivate: false
data:
global-base: 4444
exampleString: globalenv0
subEnvironments:
- name: New Environment
meta:
id: env_a9aa1b50ed15403aacc30d50be6a11a7
created: 1728549316977
modified: 1728549426890
isPrivate: true
data:
global-sub: 55555
exampleString: globalenv1
baseSourcedVar: "{{ _['global-base'] }}"
@@ -0,0 +1,25 @@
type: environment.insomnia.rest/5.0
schema_version: "5.1"
name: project-environment-source
meta:
id: wrk_5c1e0a7d93f24b64a1f0e2d3c4b5a697
created: 1736618596041
modified: 1736618596041
environments:
name: Base Environment
meta:
id: env_6c1e0a7d93f24b64a1f0e2d3c4b5a698
created: 1728549301585
modified: 1728549418314
isPrivate: false
data:
text_base: fromProjectBase
subEnvironments:
- name: Source Sub Environment
meta:
id: env_7c1e0a7d93f24b64a1f0e2d3c4b5a699
created: 1728549316977
modified: 1728549426890
isPrivate: false
data:
text_ref: '{{ _.text_base }}'
@@ -0,0 +1,192 @@
import path from "node:path";
import { faker } from "@faker-js/faker";
import { expect } from "@playwright/test";
import { ProjectType } from "../../../enums/project-types";
import { test } from "../../../misc/fixtures";
import { Collection } from "../../../models/collection";
import { Project } from "../../../models/project";
import type { FlowManager } from "../../../flows/flow-manager";
import type { VariableSourceLanding } from "../../../flows/variable-source.flow";
const cascadeFixture = path.join(__dirname, "environment-cascade-collection.yaml");
const globalCollectionFixture = path.join(__dirname, "collection-for-global-environments.yaml");
const globalEnvironmentFixture = path.join(__dirname, "global-environment.yaml");
interface Scenario {
id: string;
collection: string;
fixtures: string[];
request: string;
environment: string;
linkedProjectEnvironment?: string;
field: string;
snippet: string;
chipName: string;
value: string;
source: string;
landing: VariableSourceLanding;
landingRow?: string;
}
const scenarios: Scenario[] = [
{
id: "collection base environment",
collection: "Environment Cascade",
fixtures: [cascadeFixture],
request: "cascade request no folder",
environment: "Base Environment",
field: "cascadeVar",
snippet: "cascadeVar",
chipName: "_.cascadeVar",
value: "fromBase",
source: "Collection Environment : Base Environment",
landing: "environments-modal",
landingRow: "Base Environment",
},
{
id: "collection sub environment overriding base",
collection: "Environment Cascade",
fixtures: [cascadeFixture],
request: "cascade request no folder",
environment: "SubEnvA",
field: "cascadeVar",
snippet: "cascadeVar",
chipName: "_.cascadeVar",
value: "fromSubA",
source: "Collection Environment : SubEnvA",
landing: "environments-modal",
landingRow: "SubEnvA",
},
{
id: "folder environment winning over base and sub",
collection: "Environment Cascade",
fixtures: [cascadeFixture],
request: "cascade request",
environment: "Base Environment",
field: "cascadeVar",
snippet: "cascadeVar",
chipName: "_.cascadeVar",
value: "fromFolder",
source: "Folder Environment : cascadeFolder",
landing: "folder-pane",
},
{
id: "nested folder environment overriding the outer folder",
collection: "Environment Cascade",
fixtures: [cascadeFixture],
request: "inner request",
environment: "Base Environment",
field: "folderOverride",
snippet: "folderOverride",
chipName: "_.folderOverride",
value: "fromInnerFolder",
source: "Folder Environment : innerFolder",
landing: "folder-pane",
},
{
id: "project environment",
collection: "collection-for-global-environments",
fixtures: [globalCollectionFixture, globalEnvironmentFixture],
request: "Global Var Request",
environment: "Base Environment",
linkedProjectEnvironment: "global-environment",
field: "global-base",
snippet: "global-base",
chipName: "_['global-base']",
value: "4444",
source: "Project Environment : Base Environment",
landing: "environment-page",
landingRow: "Base Environment",
},
];
async function selectScenarioEnvironment(
flowManager: FlowManager,
collection: Collection,
scenario: Scenario,
): Promise<void> {
const { environmentFlow, variableSourceFlow } = flowManager;
if (scenario.linkedProjectEnvironment) {
await variableSourceFlow.selectProjectEnvironment(
collection,
scenario.linkedProjectEnvironment,
);
return;
}
await environmentFlow.activate(collection, scenario.environment);
}
for (const scenario of scenarios) {
test(`Verify ${scenario.id} is sent, previewed in the tag modal, and opens its source from the modal`, async ({
user,
}) => {
const { workspaceFlow, importFlow, httpRequestFlow, variableSourceFlow } =
user.flowManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
for (const fixture of scenario.fixtures) {
await importFlow.importFile(project, fixture);
}
const collection = new Collection(scenario.collection);
await selectScenarioEnvironment(user.flowManager, collection, scenario);
const request = await httpRequestFlow.get(scenario.request);
const response = await httpRequestFlow.send(request!);
const modal = await variableSourceFlow.inspectVariableModal(
scenario.snippet,
scenario.value,
);
await variableSourceFlow.openSourceFromModal();
const navigation = await variableSourceFlow.getNavigation(
scenario.landing,
scenario.landingRow,
);
expect(response.statusCode).toBe(200);
expect(
JSON.parse((response.body as { data: string }).data)[scenario.field],
).toBe(scenario.value);
expect(modal.source).toContain(scenario.source);
expect(modal.preview).toBe(scenario.value);
expect(navigation.selectedEnvironmentRow).toBe(
scenario.landingRow === undefined ? undefined : true,
);
expect(navigation.environmentTabSelected).toBe(
scenario.landing === "folder-pane" ? true : undefined,
);
expect(navigation.url).toMatch(
scenario.landing === "folder-pane"
? /debug\/request-group\//
: scenario.landing === "environment-page"
? /\/environment\/?$/
: /.*/,
);
});
test(`Verify hovering the ${scenario.id} tag shows its value and source in the tooltip`, async ({
user,
}) => {
const { workspaceFlow, importFlow, httpRequestFlow, variableSourceFlow } =
user.flowManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
for (const fixture of scenario.fixtures) {
await importFlow.importFile(project, fixture);
}
const collection = new Collection(scenario.collection);
await selectScenarioEnvironment(user.flowManager, collection, scenario);
await httpRequestFlow.get(scenario.request);
const hovered = await variableSourceFlow.hoverVariable(scenario.snippet);
expect(hovered.tooltip).toContain(scenario.value);
expect(hovered.tooltip).toContain(scenario.source);
expect(hovered.chip).toBe(scenario.chipName);
});
}
@@ -0,0 +1,50 @@
import path from "node:path";
import { faker } from "@faker-js/faker";
import { expect } from "@playwright/test";
import { ProjectType } from "../../../enums/project-types";
import { test } from "../../../misc/fixtures";
import { Collection } from "../../../models/collection";
import { Project } from "../../../models/project";
test("Verify the tag modal's source follows the edited template and hides for non-variable content", async ({
user,
}) => {
const { workspaceFlow, importFlow, environmentFlow, httpRequestFlow, variableSourceFlow } =
user.flowManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
await importFlow.importFile(
project,
path.join(__dirname, "environment-cascade-collection.yaml"),
);
const collection = new Collection("Environment Cascade");
await environmentFlow.activate(collection, "Base Environment");
await httpRequestFlow.get("cascade request");
const initial = await variableSourceFlow.inspectVariableModal("cascadeVar", "fromFolder");
const switched = await variableSourceFlow.selectModalVariable(
"{{ _.subOnlyVar }}",
"subOnly_base",
);
await variableSourceFlow.selectModalVariable({ label: "-- Custom --" });
const plainText = await variableSourceFlow.setModalTemplate("plain text", "plain text");
const inheritedKey = await variableSourceFlow.setModalTemplate("{{ constructor }}");
const padded = await variableSourceFlow.setModalTemplate("{{ _.subOnlyVar }}", "subOnly_base");
await variableSourceFlow.openSourceFromModal();
const navigation = await variableSourceFlow.getNavigation(
"environments-modal",
"Base Environment",
);
expect(initial.source).toContain("Folder Environment : cascadeFolder");
expect(switched.source).toContain("Collection Environment : Base Environment");
expect(plainText.source).toBeUndefined();
expect(inheritedKey.source).toBeUndefined();
expect(padded.source).toContain("Collection Environment : Base Environment");
expect(navigation.selectedEnvironmentRow).toBe(true);
expect(navigation.manageEnvironmentsModalCount).toBe(1);
});
@@ -0,0 +1,57 @@
import path from "node:path";
import { faker } from "@faker-js/faker";
import { expect } from "@playwright/test";
import { ProjectType } from "../../../enums/project-types";
import { test } from "../../../misc/fixtures";
import { Collection } from "../../../models/collection";
import { Project } from "../../../models/project";
test("Verify hovering an undefined variable swaps the tooltip to the render error instead of vanishing", async ({
user,
}) => {
const { workspaceFlow, importFlow, environmentFlow, httpRequestFlow, variableSourceFlow } =
user.flowManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
await importFlow.importFile(
project,
path.join(__dirname, "collection-for-global-environments.yaml"),
);
const collection = new Collection("collection-for-global-environments");
await environmentFlow.activate(collection, "ExampleA");
await httpRequestFlow.get("New Request");
const tooltips = await variableSourceFlow.hoverBetweenVariables(
"exampleString",
"subenvA0",
"global-base",
"Failed to render environment variables",
);
expect(tooltips.first).toContain("subenvA0");
expect(tooltips.second).toContain("Failed to render environment variables");
});
test("Verify hovering the tooltip's own content keeps it visible", async ({ user }) => {
const { workspaceFlow, importFlow, environmentFlow, httpRequestFlow, variableSourceFlow } =
user.flowManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
await importFlow.importFile(
project,
path.join(__dirname, "environment-cascade-collection.yaml"),
);
const collection = new Collection("Environment Cascade");
await environmentFlow.activate(collection, "Base Environment");
await httpRequestFlow.get("cascade request no folder");
const stillVisible = await variableSourceFlow.hoverTooltipContent("cascadeVar");
expect(stillVisible).toBe(true);
});
@@ -0,0 +1,154 @@
import path from "node:path";
import { faker } from "@faker-js/faker";
import { expect } from "@playwright/test";
import { ProjectType } from "../../../enums/project-types";
import { test } from "../../../misc/fixtures";
import { Collection } from "../../../models/collection";
import { Project } from "../../../models/project";
const cascadeFixture = path.join(__dirname, "environment-cascade-collection.yaml");
const globalCollectionFixture = path.join(__dirname, "collection-for-global-environments.yaml");
const globalEnvironmentFixture = path.join(__dirname, "global-environment.yaml");
const projectEnvironmentSourceFixture = path.join(__dirname, "project-environment-source.yaml");
test("Verify the tooltip's Open inside the Manage Environments modal selects the source environment, and works again after selecting another", async ({
user,
}) => {
const { workspaceFlow, importFlow, environmentFlow, httpRequestFlow, variableSourceFlow } =
user.flowManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
await importFlow.importFile(project, cascadeFixture);
const collection = new Collection("Environment Cascade");
await environmentFlow.activate(collection, "SubEnvA");
await httpRequestFlow.get("cascade request no folder");
await variableSourceFlow.openManageEnvironmentsModal();
const hovered = await variableSourceFlow.hoverVariableInModal("baseOnlyVar");
await variableSourceFlow.openSourceFromTooltip();
const firstOpen = await variableSourceFlow.getNavigation("environments-modal", "Base Environment");
await variableSourceFlow.selectModalEnvironment("SubEnvA");
await variableSourceFlow.hoverVariableInModal("baseOnlyVar");
await variableSourceFlow.openSourceFromTooltip();
const secondOpen = await variableSourceFlow.getNavigation("environments-modal", "Base Environment");
expect(hovered.tooltip).toContain("baseOnlyValue");
expect(firstOpen.selectedEnvironmentRow).toBe(true);
expect(firstOpen.manageEnvironmentsModalCount).toBe(1);
expect(secondOpen.selectedEnvironmentRow).toBe(true);
expect(secondOpen.manageEnvironmentsModalCount).toBe(1);
});
test("Verify the tooltip's Open closes the Manage Environments modal when it navigates to a folder environment", async ({
user,
}) => {
const { workspaceFlow, importFlow, environmentFlow, httpRequestFlow, variableSourceFlow } =
user.flowManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
await importFlow.importFile(project, cascadeFixture);
const collection = new Collection("Environment Cascade");
await environmentFlow.activate(collection, "SubEnvA");
await httpRequestFlow.get("cascade request");
await variableSourceFlow.openManageEnvironmentsModal();
const hovered = await variableSourceFlow.hoverVariableInModal("folderOverride");
await variableSourceFlow.openSourceFromTooltip();
await variableSourceFlow.waitForManageEnvironmentsModalClosed();
const navigation = await variableSourceFlow.getNavigation("folder-pane");
expect(hovered.tooltip).toContain("Folder Environment : cascadeFolder");
expect(navigation.url).toMatch(/debug\/request-group\//);
expect(navigation.environmentTabSelected).toBe(true);
expect(navigation.manageEnvironmentsModalCount).toBe(0);
});
test("Verify the tooltip's Open navigates to a project environment source from the Manage Environments modal and from the request body", async ({
user,
}) => {
const { workspaceFlow, importFlow, httpRequestFlow, variableSourceFlow } =
user.flowManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
await importFlow.importFile(project, globalCollectionFixture);
await importFlow.importFile(project, globalEnvironmentFixture);
const collection = new Collection("collection-for-global-environments");
await variableSourceFlow.selectProjectEnvironment(collection, "global-environment");
await httpRequestFlow.get("Global Var Request");
await variableSourceFlow.openManageEnvironmentsModal();
await variableSourceFlow.selectModalEnvironment("ExampleA");
const hoveredInModal = await variableSourceFlow.hoverVariableInModal("global-base");
await variableSourceFlow.openSourceFromTooltip();
await variableSourceFlow.waitForManageEnvironmentsModalClosed();
const fromModal = await variableSourceFlow.getNavigation("environment-page", "Base Environment");
await httpRequestFlow.get("Global Var Request");
const hoveredInBody = await variableSourceFlow.hoverVariable("global-base");
await variableSourceFlow.openSourceFromTooltip();
const fromBody = await variableSourceFlow.getNavigation("environment-page", "Base Environment");
expect(hoveredInModal.tooltip).toContain("Project Environment : Base Environment");
expect(fromModal.url).toMatch(/\/environment/);
expect(fromModal.manageEnvironmentsModalCount).toBe(0);
expect(fromModal.selectedEnvironmentRow).toBe(true);
expect(hoveredInBody.tooltip).toContain("Project Environment : Base Environment");
expect(fromBody.url).toMatch(/\/environment\/?$/);
expect(fromBody.selectedEnvironmentRow).toBe(true);
});
test("Verify the tooltip's Open selects the source environment on the project environment page", async ({
user,
}) => {
const { workspaceFlow, importFlow, variableSourceFlow } =
user.flowManager;
const { workspacePage, environmentPage } = user.pageManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
await importFlow.importFile(project, globalEnvironmentFixture);
await workspacePage.clickNode(
await workspacePage.resolveNode({ name: "global-environment" }),
);
await environmentPage.selectEnvironment("New Environment");
const hovered = await variableSourceFlow.hoverEnvironmentPageVariable("global-base");
await variableSourceFlow.openSourceFromTooltip();
const navigation = await variableSourceFlow.getNavigation("environment-page", "Base Environment");
expect(hovered.tooltip).toContain("Project Environment : Base Environment");
expect(navigation.selectedEnvironmentRow).toBe(true);
});
test("Verify the project environment page attributes its own environments to the project", async ({
user,
}) => {
const { workspaceFlow, importFlow, variableSourceFlow } = user.flowManager;
const { workspacePage, environmentPage } = user.pageManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
await importFlow.importFile(project, projectEnvironmentSourceFixture);
await workspacePage.clickNode(
await workspacePage.resolveNode({ name: "project-environment-source" }),
);
await environmentPage.selectEnvironment("Source Sub Environment");
const modal = await variableSourceFlow.inspectEnvironmentPageVariable(
"text_base",
"fromProjectBase",
);
expect(modal.source).toContain("Project Environment : Base Environment");
expect(modal.preview).toBe("fromProjectBase");
});
@@ -0,0 +1,67 @@
import path from "node:path";
import { faker } from "@faker-js/faker";
import { expect } from "@playwright/test";
import { HttpMethod } from "../../enums/http-method";
import { ProjectType } from "../../enums/project-types";
import { certPath } from "../../misc/certs";
import { DEFAULT_TIMEOUT, HTTP_SERVER_MTLS, test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
import { Project } from "../../models/project";
const url = `${HTTP_SERVER_MTLS}/post`;
test("Verify pre-request script reads and replaces the client certificate via insomnia.request.certificate", async ({
user,
}) => {
const { workspaceFlow, httpRequestFlow, preferencesFlow, certificatesFlow } =
user.flowManager;
await preferencesFlow.set({
validateSSL: false,
dataFolders: [path.join(__dirname, "../../misc/fixtures")],
});
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
const collection = await workspaceFlow.createCollection(
project,
new Collection(faker.string.alphanumeric(10)),
);
await certificatesFlow.addClientCertificate(collection, {
host: "localhost",
cert: certPath("mtls-client-cert.pem"),
key: certPath("mtls-client-key.pem"),
});
const request = await httpRequestFlow.create(collection, {
name: faker.string.alphanumeric(10),
method: HttpMethod.Post,
url,
preRequestScript: `
console.log('original cert:', JSON.stringify(insomnia.request.certificate.cert));
console.log('original key:', JSON.stringify(insomnia.request.certificate.key));
insomnia.request.certificate.update({
disabled: false,
key: { src: '${certPath("mtls-client-key.pem")}' },
cert: { src: '${certPath("mtls-client-cert.pem")}' },
passphrase: '',
});
`,
});
const responseView = await httpRequestFlow.send(request);
expect(responseView.statusCode).toBe(200);
await expect
.poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT })
.toContain("Adding SSL PEM certificate");
await expect
.poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT })
.toContain("Adding SSL KEY certificate");
await expect
.poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT })
.toContain("original cert:");
});
@@ -0,0 +1,59 @@
import { faker } from "@faker-js/faker";
import { expect } from "@playwright/test";
import { HttpMethod } from "../../enums/http-method";
import { ProjectType } from "../../enums/project-types";
import { DEFAULT_TIMEOUT, HTTP_SERVER, test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
import { Project } from "../../models/project";
const url = `${HTTP_SERVER}/post`;
test("Verify pre-request script reads the configured proxy and overrides it via insomnia.request.proxy", async ({
user,
}) => {
const { workspaceFlow, httpRequestFlow, preferencesFlow } = user.flowManager;
await preferencesFlow.set({
timeout: 5000,
proxyEnabled: true,
httpProxy: "localhost:1111",
httpsProxy: "localhost:2222",
noProxy: "http://a.com,https://b.com",
});
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
const collection = await workspaceFlow.createCollection(
project,
new Collection(faker.string.alphanumeric(10)),
);
const request = await httpRequestFlow.create(collection, {
name: faker.string.alphanumeric(10),
method: HttpMethod.Post,
url,
preRequestScript: `
console.log(insomnia.request.proxy.getProxyUrl());
insomnia.request.proxy.update({
host: '127.0.0.1',
match: '<all_urls>',
port: 8888,
tunnel: false,
authenticate: false,
username: '',
password: '',
});
`,
});
const responseView = await httpRequestFlow.send(request);
expect(responseView.statusCode).toBeUndefined();
await expect
.poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT })
.toContain("localhost:1111");
await expect
.poll(() => responseView.console?.(), { timeout: DEFAULT_TIMEOUT })
.toContain("Trying 127.0.0.1:8888");
});
@@ -0,0 +1,105 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { faker } from "@faker-js/faker";
import { expect } from "@playwright/test";
import { HttpMethod } from "../../enums/http-method";
import { ProjectType } from "../../enums/project-types";
import { ResponseExportOption } from "../../enums/response-export-option";
import { DEFAULT_TIMEOUT, HTTP_SERVER, LONG_TIMEOUT, test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
import { Project } from "../../models/project";
const LARGE_RESPONSE_BYTES = 5 * 1024 * 1024;
test("Verify exporting a small JSON response as raw, prettified and HTTP debug files", async ({
user,
}) => {
const { workspaceFlow, httpRequestFlow } = user.flowManager;
const exportDir = fs.mkdtempSync(path.join(os.tmpdir(), "insomnia-export-"));
const rawPath = path.join(exportDir, "response-raw.json");
const prettifiedPath = path.join(exportDir, "response-pretty.json");
const debugPath = path.join(exportDir, "response-debug.txt");
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
const collection = await workspaceFlow.createCollection(
project,
new Collection(faker.string.alphanumeric(10)),
);
const marker = faker.string.alphanumeric(10);
const request = await httpRequestFlow.create(collection, {
name: faker.string.alphanumeric(10),
method: HttpMethod.Post,
url: `${HTTP_SERVER}/post?marker=${marker}`,
});
await httpRequestFlow.send(request);
await httpRequestFlow.exportResponse(ResponseExportOption.Raw, rawPath);
await httpRequestFlow.exportResponse(ResponseExportOption.Prettified, prettifiedPath);
await httpRequestFlow.exportResponse(ResponseExportOption.HttpDebug, debugPath);
await expect
.poll(() => [rawPath, prettifiedPath, debugPath].every((file) => fs.existsSync(file) && fs.statSync(file).size > 0), {
timeout: DEFAULT_TIMEOUT,
})
.toBe(true);
const raw = fs.readFileSync(rawPath, "utf8");
const prettified = fs.readFileSync(prettifiedPath, "utf8");
const debug = fs.readFileSync(debugPath, "utf8");
fs.rmSync(exportDir, { recursive: true, force: true });
expect(JSON.parse(raw)).toMatchObject({ args: { marker } });
expect(JSON.parse(prettified)).toEqual(JSON.parse(raw));
expect(prettified.length).toBeGreaterThan(raw.length);
expect(debug).toContain("Content-Type");
expect(debug).toContain(marker);
});
test("Verify a large JSON response can be exported raw or saved to file, and prettified export is disabled", async ({
user,
}) => {
const { workspaceFlow, httpRequestFlow } = user.flowManager;
const exportDir = fs.mkdtempSync(path.join(os.tmpdir(), "insomnia-export-"));
const rawPath = path.join(exportDir, "response-large.json");
const savedPath = path.join(exportDir, "response-saved.json");
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
const collection = await workspaceFlow.createCollection(
project,
new Collection(faker.string.alphanumeric(10)),
);
const request = await httpRequestFlow.create(collection, {
name: faker.string.alphanumeric(10),
method: HttpMethod.Get,
url: `${HTTP_SERVER}/large-json`,
});
await httpRequestFlow.send(request);
const prettifiedState = await httpRequestFlow.getResponseExportOptionState(
ResponseExportOption.Prettified,
);
await httpRequestFlow.exportResponse(ResponseExportOption.Raw, rawPath);
await httpRequestFlow.saveResponseToFile(savedPath);
await expect
.poll(() => [rawPath, savedPath].every((file) => fs.existsSync(file) && fs.statSync(file).size > LARGE_RESPONSE_BYTES), {
timeout: LONG_TIMEOUT,
})
.toBe(true);
const rawSize = fs.statSync(rawPath).size;
const savedHead = fs.readFileSync(savedPath).subarray(0, 16).toString("utf8");
fs.rmSync(exportDir, { recursive: true, force: true });
expect(rawSize).toBeGreaterThan(LARGE_RESPONSE_BYTES);
expect(savedHead).toContain('{"items"');
expect(prettifiedState.text).toContain("must be <5MB");
expect(prettifiedState.disabled).toBe(true);
});
@@ -0,0 +1,58 @@
import { faker } from "@faker-js/faker";
import { expect } from "@playwright/test";
import { ContentType } from "../../enums/content-type";
import { HttpMethod } from "../../enums/http-method";
import { ProjectType } from "../../enums/project-types";
import { HTTP_SERVER, test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
import { Project } from "../../models/project";
test("Verify a prompt Tag Caches its Value Under the Storage Key Until it is Cleared", async ({
user,
}) => {
const { workspaceFlow, httpRequestFlow, templateTagFlow } = user.flowManager;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
const collection = await workspaceFlow.createCollection(
project,
new Collection(faker.string.alphanumeric(10)),
);
const label = faker.string.alpha(12);
const storageKey = faker.string.alphanumeric(10);
const promptTag = `{% prompt '${label}', 'Enter secret', '', '${storageKey}', true, false %}`;
const request = await httpRequestFlow.create(collection, {
name: faker.string.alphanumeric(10),
method: HttpMethod.Post,
url: `${HTTP_SERVER}/post`,
body: { mimeType: ContentType.Plain, text: promptTag },
});
const firstValue = faker.string.alphanumeric(12);
const secondValue = faker.string.alphanumeric(12);
const previewBeforeSend = await templateTagFlow.get(promptTag);
const first = await templateTagFlow.sendAnsweringPrompt(
request,
label,
firstValue,
);
const cached = await httpRequestFlow.send(request);
await templateTagFlow.clearCachedPrompt(promptTag);
const second = await templateTagFlow.sendAnsweringPrompt(
request,
label,
secondValue,
);
expect(previewBeforeSend.functionName).toBe("prompt");
expect(first.promptInputType).toBe("password");
expect(first.response.statusCode).toBe(200);
expect(first.response.body).toMatchObject({ data: firstValue });
expect(cached.statusCode).toBe(200);
expect(cached.body).toMatchObject({ data: firstValue });
expect(second.response.statusCode).toBe(200);
expect(second.response.body).toMatchObject({ data: secondValue });
});
@@ -0,0 +1,63 @@
import { faker } from "@faker-js/faker";
import { expect } from "@playwright/test";
import { ContentType } from "../../enums/content-type";
import { HttpMethod } from "../../enums/http-method";
import { ProjectType } from "../../enums/project-types";
import { HTTP_SERVER, test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
import { Project } from "../../models/project";
test("Verify the response Tag's Trigger Behavior Controls When its Dependency Request is Resent", async ({
user,
}) => {
const { workspaceFlow, httpRequestFlow } = user.flowManager;
const { page } = user;
const project = await workspaceFlow.createProject(
new Project(faker.string.alphanumeric(10), ProjectType.Local),
);
const collection = await workspaceFlow.createCollection(
project,
new Collection(faker.string.alphanumeric(10)),
);
const marker = faker.string.alphanumeric(10);
const dependency = await httpRequestFlow.create(collection, {
name: faker.string.alphanumeric(10),
method: HttpMethod.Post,
url: `${HTTP_SERVER}/post`,
body: { mimeType: ContentType.JSON, text: JSON.stringify({ marker }) },
});
const filter = `b64::${Buffer.from("$.json.marker").toString("base64")}::46b`;
const createDependent = (trigger: string, maxAge: number) =>
httpRequestFlow.create(collection, {
name: faker.string.alphanumeric(10),
method: HttpMethod.Post,
url: `${HTTP_SERVER}/post`,
body: {
mimeType: ContentType.Plain,
text: `{% response 'body', '${dependency.id}', '${filter}', '${trigger}', ${maxAge} %}`,
},
});
const neverRequest = await createDependent("never", 60);
const noHistoryRequest = await createDependent("no-history", 60);
const whenExpiredRequest = await createDependent("when-expired", 0);
const alwaysRequest = await createDependent("always", 60);
const neverFailure = httpRequestFlow.send(neverRequest);
await neverFailure.catch(() => {});
await page.keyboard.press("Escape");
const noHistory = await httpRequestFlow.send(noHistoryRequest);
const whenExpired = await httpRequestFlow.send(whenExpiredRequest);
const always = await httpRequestFlow.send(alwaysRequest);
await expect(neverFailure).rejects.toThrow(/No responses for request/);
expect(noHistory.statusCode).toBe(200);
expect(noHistory.body).toMatchObject({ data: marker });
expect(whenExpired.statusCode).toBe(200);
expect(whenExpired.body).toMatchObject({ data: marker });
expect(always.statusCode).toBe(200);
expect(always.body).toMatchObject({ data: marker });
});