test(insomnia-test): add git-sync, cloud-sync, organization and konnect specs

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Bob QiuandClaude Sonnet 5.5 committed 2026-11-05 14:18:22 +08:00
1 parent b3ab6ac158
commit ebd3da9782
40 files changed
+416 -317

No files matched your search

@@ -1,19 +1,20 @@
import { faker } from "@faker-js/faker";
import { test } from "@package/misc/fixtures";
import { expect } from "@playwright/test";
import { test } from "../../misc/fixtures";
test("Verify branch, commit, merge, and restore actions on one Cloud-synced workspace do not affect another workspace's branches or content", async ({
user,
}) => {
const { workspaceFlow, httpRequestFlow, mcpClientFlow, cloudSyncFlow } =
user.flowManager;
const { httpRequestPage, mcpClientPage, cloudSyncPage, cloudSyncBranchesDialogPage } = user.pageManager;
const { httpRequestPage, mcpClientPage, cloudSyncPage } = user.pageManager;
await cloudSyncFlow.fetch("My Collection R1");
const collectionX = await workspaceFlow.getCollection({
name: "My Collection R1",
});
await httpRequestFlow.get("New Request");
await httpRequestFlow.get("New Request", collectionX);
await httpRequestPage.setUrl(faker.internet.url());
const branchX = faker.git.branch();
await cloudSyncFlow.createBranch(branchX);
@@ -24,6 +25,7 @@ test("Verify branch, commit, merge, and restore actions on one Cloud-synced work
await user.page.reload();
const requestXAfterRestore = await httpRequestFlow.get(
"New Request",
collectionX,
);
await cloudSyncFlow.fetch("My MCP Client");
@@ -38,16 +40,16 @@ test("Verify branch, commit, merge, and restore actions on one Cloud-synced work
await user.page.reload();
const clientYAfterRestore = await mcpClientFlow.get("My MCP Client");
await httpRequestFlow.get("New Request");
await httpRequestFlow.get("New Request", collectionX);
await cloudSyncPage.openBranchesDialog();
const branchXListedOnX = await cloudSyncBranchesDialogPage.isLocalBranchListed(branchX);
const branchYListedOnX = await cloudSyncBranchesDialogPage.isLocalBranchListed(branchY);
await cloudSyncBranchesDialogPage.closeBranchesDialog();
const branchXListedOnX = await cloudSyncPage.isLocalBranchListed(branchX);
const branchYListedOnX = await cloudSyncPage.isLocalBranchListed(branchY);
await cloudSyncPage.closeBranchesDialog();
await mcpClientFlow.get("My MCP Client");
await cloudSyncPage.openBranchesDialog();
const branchYListedOnY = await cloudSyncBranchesDialogPage.isLocalBranchListed(branchY);
const branchXListedOnY = await cloudSyncBranchesDialogPage.isLocalBranchListed(branchX);
await cloudSyncBranchesDialogPage.closeBranchesDialog();
const branchYListedOnY = await cloudSyncPage.isLocalBranchListed(branchY);
const branchXListedOnY = await cloudSyncPage.isLocalBranchListed(branchX);
await cloudSyncPage.closeBranchesDialog();
expect(requestXAfterRestore?.url).toBe("");
expect(clientYAfterRestore?.url).toBe("");
@@ -1,36 +1,37 @@
import { faker } from "@faker-js/faker";
import { DEFAULT_TIMEOUT, test } from "@package/misc/fixtures";
import { expect } from "@playwright/test";
import { DEFAULT_TIMEOUT, test } from "../../misc/fixtures";
test("Verify fetching a remote branch, checking out, deleting a local branch, and creating a new branch on a Cloud-synced workspace", async ({
user,
}) => {
const { cloudSyncFlow } = user.flowManager;
const { cloudSyncPage, cloudSyncBranchesDialogPage } = user.pageManager;
const { cloudSyncPage } = user.pageManager;
await cloudSyncFlow.fetch("My MCP Client");
await cloudSyncPage.openBranchesDialog();
await cloudSyncBranchesDialogPage.fetchRemoteBranch("develop");
await cloudSyncPage.fetchRemoteBranch("develop");
await expect
.poll(() => cloudSyncBranchesDialogPage.isLocalBranchListed("develop"), {
.poll(() => cloudSyncPage.isLocalBranchListed("develop"), {
timeout: DEFAULT_TIMEOUT,
})
.toBe(true);
await cloudSyncBranchesDialogPage.checkoutLocalBranch("master");
await cloudSyncBranchesDialogPage.deleteLocalBranch("develop");
await cloudSyncPage.checkoutLocalBranch("master");
await cloudSyncPage.deleteLocalBranch("develop");
await cloudSyncBranchesDialogPage.waitForLocalBranchRemoved("develop");
await cloudSyncPage.waitForLocalBranchRemoved("develop");
const newBranch = faker.git.branch();
await cloudSyncBranchesDialogPage.createBranch(newBranch);
await cloudSyncPage.createBranch(newBranch);
await expect
.poll(() => cloudSyncBranchesDialogPage.isLocalBranchListed(newBranch), {
.poll(() => cloudSyncPage.isLocalBranchListed(newBranch), {
timeout: DEFAULT_TIMEOUT,
})
.toBe(true);
expect(await cloudSyncBranchesDialogPage.isLocalBranchDeleteDisabled(newBranch)).toBe(true);
await cloudSyncBranchesDialogPage.closeBranchesDialog();
expect(await cloudSyncPage.isLocalBranchDeleteDisabled(newBranch)).toBe(true);
await cloudSyncPage.closeBranchesDialog();
});
@@ -2,12 +2,11 @@ import * as fs from "node:fs";
import path from "node:path";
import { faker } from "@faker-js/faker";
import { readBackendProjectMetas } from "@package/misc/backend-projects";
import { getCloudSyncGraphQLRequestLog, setCloudSyncGraphQLDelay } from "@package/misc/cloud-sync-mock";
import { MOCK_API_SERVER, resetCloudSyncState, SESSION_ID, test } from "@package/misc/fixtures";
import { Collection } from "@package/models/collection";
import { expect } from "@playwright/test";
import { getCloudSyncGraphQLRequestLog, MOCK_API_SERVER, resetCloudSyncState, SESSION_ID, setCloudSyncGraphQLDelay, test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
// Every Cloud Sync workspace used to share one VCS singleton, so creating a sibling
// collection Y could repoint the singleton's `_backendProject` mid-flight,
// while workspace X's `pull()` was still awaiting a network response. X's
@@ -55,10 +54,19 @@ test("Verify creating a new collection does not misattribute a concurrent pull's
const dataPath = await appFlow.getDataPath();
const projectsDir = path.join(dataPath, "version-control", "projects");
const readProjectMeta = async (workspaceId: string) => {
const [match] = (await readBackendProjectMetas(projectsDir))
.filter((m) => m.rootDocumentId === workspaceId)
.map((m) => ({ backendProjectId: m.id, dir: m.dir }));
return match;
const dirs = await fs.promises.readdir(projectsDir);
for (const dir of dirs) {
const meta = JSON.parse(
await fs.promises.readFile(
path.join(projectsDir, dir, "meta.json"),
"utf8",
),
);
if (meta.rootDocumentId === workspaceId) {
return { backendProjectId: meta.id as string, dir };
}
}
return;
};
const countBlobFiles = async (dir: string) => {
const blobsDir = path.join(projectsDir, dir, "blobs");
@@ -2,13 +2,12 @@ import * as fs from "node:fs";
import path from "node:path";
import { faker } from "@faker-js/faker";
import { HttpMethod } from "@package/enums/http-method";
import { readBackendProjectMetas } from "@package/misc/backend-projects";
import { getCloudSyncProjectInfo, setCloudSyncArchiveFailure } from "@package/misc/cloud-sync-mock";
import { HTTP_SERVER, resetCloudSyncState, test } from "@package/misc/fixtures";
import { Collection } from "@package/models/collection";
import { expect } from "@playwright/test";
import { HttpMethod } from "../../enums/http-method";
import { getCloudSyncProjectInfo, HTTP_SERVER, resetCloudSyncState, setCloudSyncArchiveFailure, test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
test.afterAll(async () => {
await resetCloudSyncState();
});
@@ -18,7 +17,7 @@ test("Verify the dashboard can delete a never-pulled unsynced remote file and a
}) => {
const { workspaceFlow, httpRequestFlow, cloudSyncFlow, appFlow } =
user.flowManager;
const { cloudSyncPage, cloudSyncFileDeletePage } = user.pageManager;
const { cloudSyncPage } = user.pageManager;
const project = await workspaceFlow.getProject({
name: "Personal Workspace",
@@ -26,12 +25,12 @@ test("Verify the dashboard can delete a never-pulled unsynced remote file and a
await cloudSyncFlow.openProjectDashboard(project!);
const mcpClientInfoBefore = await getCloudSyncProjectInfo("My MCP Client");
await cloudSyncFileDeletePage.openFileDeleteDialog("My MCP Client");
const deleteDialogText = await cloudSyncFileDeletePage.getFileDeleteDialogText();
await cloudSyncPage.openFileDeleteDialog("My MCP Client");
const deleteDialogText = await cloudSyncPage.getFileDeleteDialogText();
const logLines = await appFlow.captureMainProcessLog(async () => {
await cloudSyncFileDeletePage.confirmFileDelete();
await cloudSyncPage.confirmFileDelete();
await expect
.poll(() => cloudSyncFileDeletePage.isFileCardVisible("My MCP Client"))
.poll(() => cloudSyncPage.isFileCardVisible("My MCP Client"))
.toBe(false);
});
const mcpClientInfoAfter = await getCloudSyncProjectInfo("My MCP Client");
@@ -39,15 +38,15 @@ test("Verify the dashboard can delete a never-pulled unsynced remote file and a
const ghostInfoBefore = await getCloudSyncProjectInfo("Ghost Collection");
await cloudSyncFlow.deleteUnsyncedFile("Ghost Collection");
await expect
.poll(() => cloudSyncFileDeletePage.isFileCardVisible("Ghost Collection"))
.poll(() => cloudSyncPage.isFileCardVisible("Ghost Collection"))
.toBe(false);
const ghostInfoAfter = await getCloudSyncProjectInfo("Ghost Collection");
await setCloudSyncArchiveFailure(true);
await cloudSyncFlow.deleteUnsyncedFile("My Environment");
await cloudSyncFileDeletePage.waitForFileDeleteFailureToast("My Environment");
await cloudSyncPage.waitForFileDeleteFailureToast("My Environment");
const environmentStillVisible =
await cloudSyncFileDeletePage.isFileCardVisible("My Environment");
await cloudSyncPage.isFileCardVisible("My Environment");
const environmentInfoAfterFailure =
await getCloudSyncProjectInfo("My Environment");
await setCloudSyncArchiveFailure(false);
@@ -68,10 +67,19 @@ test("Verify the dashboard can delete a never-pulled unsynced remote file and a
const dataPath = await appFlow.getDataPath();
const projectsDir = path.join(dataPath, "version-control", "projects");
const readProjectMeta = async (workspaceId: string) => {
const [match] = (await readBackendProjectMetas(projectsDir))
.filter((m) => m.rootDocumentId === workspaceId)
.map((m) => ({ rootDocumentId: m.rootDocumentId, dir: m.dir }));
return match;
const dirs = await fs.promises.readdir(projectsDir);
for (const dir of dirs) {
const meta = JSON.parse(
await fs.promises.readFile(
path.join(projectsDir, dir, "meta.json"),
"utf8",
),
);
if (meta.rootDocumentId === workspaceId) {
return { rootDocumentId: meta.rootDocumentId as string, dir };
}
}
return;
};
const readLatestSnapshotState = async (dir: string) => {
const snapshotsDir = path.join(projectsDir, dir, "snapshots");
@@ -1,11 +1,11 @@
import * as fs from "node:fs";
import path from "node:path";
import { DeleteMode } from "@package/enums/delete-mode";
import { readBackendProjectMetas } from "@package/misc/backend-projects";
import { resetCloudSyncState, test } from "@package/misc/fixtures";
import { expect } from "@playwright/test";
import { DeleteMode } from "../../enums/delete-mode";
import { resetCloudSyncState, test } from "../../misc/fixtures";
test.afterAll(async () => {
await resetCloudSyncState();
});
@@ -20,9 +20,20 @@ test("Verify deleting a synced Cloud Sync workspace archives its own backend pro
const workspace = await workspaceFlow.get({ name: "My Environment" });
const dataPath = await appFlow.getDataPath();
const projectsDir = path.join(dataPath, "version-control", "projects");
const backendProjectId = (await readBackendProjectMetas(projectsDir)).find(
(m) => m.rootDocumentId === workspace!.id,
)?.id;
const projectDirs = await fs.promises.readdir(projectsDir);
let backendProjectId: string | undefined;
for (const dir of projectDirs) {
const meta = JSON.parse(
await fs.promises.readFile(
path.join(projectsDir, dir, "meta.json"),
"utf8",
),
);
if (meta.rootDocumentId === workspace!.id) {
backendProjectId = meta.id;
break;
}
}
await cloudSyncPage.backToAllProjects();
const logLines = await appFlow.captureMainProcessLog(async () => {
@@ -1,8 +1,9 @@
import { DeleteMode } from "@package/enums/delete-mode";
import { SyncStatus } from "@package/enums/sync-status";
import { DEFAULT_TIMEOUT, resetCloudSyncState, test } from "@package/misc/fixtures";
import { expect } from "@playwright/test";
import { DeleteMode } from "../../enums/delete-mode";
import { SyncStatus } from "../../enums/sync-status";
import { DEFAULT_TIMEOUT, resetCloudSyncState, test } from "../../misc/fixtures";
test.afterAll(async () => {
await resetCloudSyncState();
});
@@ -1,7 +1,8 @@
import { faker } from "@faker-js/faker";
import { test } from "@package/misc/fixtures";
import { expect } from "@playwright/test";
import { test } from "../../misc/fixtures";
test("Verify discarding changes on one Cloud-synced workspace does not affect another workspace's uncommitted edits", async ({
user,
}) => {
@@ -13,7 +14,7 @@ test("Verify discarding changes on one Cloud-synced workspace does not affect an
const collectionX = await workspaceFlow.getCollection({
name: "My Collection R1",
});
const requestXBefore = await httpRequestFlow.get("New Request");
const requestXBefore = await httpRequestFlow.get("New Request", collectionX);
const editedUrlX = faker.internet.url();
await httpRequestPage.setUrl(editedUrlX);
@@ -26,6 +27,7 @@ test("Verify discarding changes on one Cloud-synced workspace does not affect an
const requestXAfterDiscard = await httpRequestFlow.get(
"New Request",
collectionX,
);
expect(clientYAfterDiscard?.url).toBe(clientYBefore?.url);
@@ -1,8 +1,9 @@
import { faker } from "@faker-js/faker";
import { ContentType } from "@package/enums/content-type";
import { DEFAULT_TIMEOUT, test } from "@package/misc/fixtures";
import { expect } from "@playwright/test";
import { ContentType } from "../../enums/content-type";
import { DEFAULT_TIMEOUT, test } from "../../misc/fixtures";
test("Verify discarding changes, committing and pushing, and restoring an earlier snapshot on a Cloud-synced workspace", async ({
user,
}) => {
@@ -13,14 +14,14 @@ test("Verify discarding changes, committing and pushing, and restoring an earlie
const collection = await workspaceFlow.getCollection({
name: "My Collection R1",
});
await httpRequestFlow.get("New Request");
await httpRequestFlow.get("New Request", collection);
await httpRequestPage.setBody({
mimeType: ContentType.Plain,
text: faker.lorem.slug(),
}, (filePath) => user.flowManager.context.stubFileChooser(filePath));
});
await cloudSyncFlow.discardAllChanges();
await user.page.reload();
await httpRequestFlow.get("New Request");
await httpRequestFlow.get("New Request", collection);
await expect
.poll(() => httpRequestPage.getBody(), { timeout: DEFAULT_TIMEOUT })
@@ -29,13 +30,13 @@ test("Verify discarding changes, committing and pushing, and restoring an earlie
await httpRequestPage.setBody({
mimeType: ContentType.Plain,
text: faker.lorem.slug(),
}, (filePath) => user.flowManager.context.stubFileChooser(filePath));
});
await cloudSyncFlow.commitAndPush("New Request", faker.lorem.sentence());
const isCommitDisabled = await cloudSyncPage.isCommitDisabled();
await cloudSyncFlow.restoreSnapshot("Initial Snapshot");
await user.page.reload();
await httpRequestFlow.get("New Request");
await httpRequestFlow.get("New Request", collection);
const bodyAfterRestore = await httpRequestPage.getBody();
expect(isCommitDisabled).toBe(true);
@@ -1,7 +1,8 @@
import { faker } from "@faker-js/faker";
import { test } from "@package/misc/fixtures";
import { expect } from "@playwright/test";
import { test } from "../../misc/fixtures";
const CHARSET_ERROR =
"Branch names must be at least 3 characters long and can only contain English letters, numbers, period (.), hyphen (-), underscore (_) and forward slash (/)";
@@ -27,25 +28,25 @@ test("Verify creating a branch with an invalid or already-used name is rejected
user,
}) => {
const { cloudSyncFlow } = user.flowManager;
const { cloudSyncPage, cloudSyncBranchesDialogPage } = user.pageManager;
const { cloudSyncPage } = user.pageManager;
await cloudSyncFlow.fetch("My Collection R1");
await cloudSyncPage.openBranchesDialog();
for (const { name, error } of INVALID_NAMES) {
await cloudSyncBranchesDialogPage.createBranch(name);
await cloudSyncBranchesDialogPage.waitForBranchCreationError(error);
await cloudSyncPage.createBranch(name);
await cloudSyncPage.waitForBranchCreationError(error);
}
const existingBranch = faker.git.branch();
await cloudSyncBranchesDialogPage.createBranch(existingBranch);
await cloudSyncPage.createBranch(existingBranch);
await expect
.poll(() => cloudSyncBranchesDialogPage.isLocalBranchListed(existingBranch))
.poll(() => cloudSyncPage.isLocalBranchListed(existingBranch))
.toBe(true);
await cloudSyncBranchesDialogPage.createBranch(existingBranch);
await cloudSyncBranchesDialogPage.waitForBranchCreationError(
await cloudSyncPage.createBranch(existingBranch);
await cloudSyncPage.waitForBranchCreationError(
`Branch already exists by name ${existingBranch}`,
);
for (const { name } of INVALID_NAMES) {
await cloudSyncBranchesDialogPage.waitForLocalBranchRemoved(name);
await cloudSyncPage.waitForLocalBranchRemoved(name);
}
});
@@ -1,8 +1,9 @@
import { faker } from "@faker-js/faker";
import { ContentType } from "@package/enums/content-type";
import { DEFAULT_TIMEOUT, test } from "@package/misc/fixtures";
import { expect } from "@playwright/test";
import { ContentType } from "../../enums/content-type";
import { DEFAULT_TIMEOUT, test } from "../../misc/fixtures";
for (const { version, chosenIndex } of [
{ version: "Current", chosenIndex: 0 },
{ version: "Incoming", chosenIndex: 1 },
@@ -11,13 +12,13 @@ for (const { version, chosenIndex } of [
user,
}) => {
const { workspaceFlow, httpRequestFlow, cloudSyncFlow } = user.flowManager;
const { httpRequestPage, cloudSyncPage, cloudSyncBranchesDialogPage } = user.pageManager;
const { httpRequestPage, cloudSyncPage } = user.pageManager;
await cloudSyncFlow.fetch("My Collection R1");
const collection = await workspaceFlow.getCollection({
name: "My Collection R1",
});
await httpRequestFlow.get("New Request");
await httpRequestFlow.get("New Request", collection);
const branch = faker.git.branch();
const branchBody = faker.lorem.slug();
@@ -25,19 +26,19 @@ for (const { version, chosenIndex } of [
await httpRequestPage.setBody({
mimeType: ContentType.Plain,
text: branchBody,
}, (filePath) => user.flowManager.context.stubFileChooser(filePath));
});
await cloudSyncFlow.commit("New Request", faker.lorem.sentence());
await cloudSyncPage.openBranchesDialog();
await cloudSyncBranchesDialogPage.checkoutLocalBranch("master");
await cloudSyncBranchesDialogPage.closeBranchesDialog();
await cloudSyncPage.checkoutLocalBranch("master");
await cloudSyncPage.closeBranchesDialog();
await user.page.reload();
await httpRequestFlow.get("New Request");
await httpRequestFlow.get("New Request", collection);
const currentBody = faker.lorem.slug();
await httpRequestPage.setBody({
mimeType: ContentType.Plain,
text: currentBody,
}, (filePath) => user.flowManager.context.stubFileChooser(filePath));
});
await cloudSyncFlow.commit("New Request", faker.lorem.sentence());
const conflicts = await cloudSyncFlow.mergeBranchResolvingConflicts(
@@ -45,7 +46,7 @@ for (const { version, chosenIndex } of [
version,
);
await user.page.reload();
await httpRequestFlow.get("New Request");
await httpRequestFlow.get("New Request", collection);
const bodies = [currentBody, branchBody];
expect(conflicts).toEqual(["New Request"]);
@@ -2,13 +2,13 @@ import * as fs from "node:fs";
import path from "node:path";
import { faker } from "@faker-js/faker";
import { HttpMethod } from "@package/enums/http-method";
import { SyncStatus } from "@package/enums/sync-status";
import { readBackendProjectMetas } from "@package/misc/backend-projects";
import { HTTP_SERVER, test } from "@package/misc/fixtures";
import { Collection } from "@package/models/collection";
import { expect } from "@playwright/test";
import { HttpMethod } from "../../enums/http-method";
import { SyncStatus } from "../../enums/sync-status";
import { HTTP_SERVER, test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
test("Verify a new collection commits and pushes under its own backend project", async ({
user,
}) => {
@@ -38,10 +38,19 @@ test("Verify a new collection commits and pushes under its own backend project",
const dataPath = await appFlow.getDataPath();
const projectsDir = path.join(dataPath, "version-control", "projects");
const readProjectMeta = async (workspaceId: string) => {
const [match] = (await readBackendProjectMetas(projectsDir))
.filter((m) => m.rootDocumentId === workspaceId)
.map((m) => ({ rootDocumentId: m.rootDocumentId, dir: m.dir }));
return match;
const dirs = await fs.promises.readdir(projectsDir);
for (const dir of dirs) {
const meta = JSON.parse(
await fs.promises.readFile(
path.join(projectsDir, dir, "meta.json"),
"utf8",
),
);
if (meta.rootDocumentId === workspaceId) {
return { rootDocumentId: meta.rootDocumentId as string, dir };
}
}
return;
};
const readLatestSnapshotState = async (dir: string) => {
const snapshotsDir = path.join(projectsDir, dir, "snapshots");
@@ -2,13 +2,13 @@ import * as fs from "node:fs";
import path from "node:path";
import { faker } from "@faker-js/faker";
import { HttpMethod } from "@package/enums/http-method";
import { SyncStatus } from "@package/enums/sync-status";
import { readBackendProjectMetas } from "@package/misc/backend-projects";
import { HTTP_SERVER, test } from "@package/misc/fixtures";
import { Collection } from "@package/models/collection";
import { expect } from "@playwright/test";
import { HttpMethod } from "../../enums/http-method";
import { SyncStatus } from "../../enums/sync-status";
import { HTTP_SERVER, test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
test("Verify a new collection is not left in an unsynced state after an app restart", async ({
user,
}) => {
@@ -49,9 +49,19 @@ test("Verify a new collection is not left in an unsynced state after an app rest
const dataPath = await appFlow.getDataPath();
const projectsDir = path.join(dataPath, "version-control", "projects");
const matchingDirs = (await readBackendProjectMetas(projectsDir))
.filter((m) => m.rootDocumentId === collectionYId)
.map((m) => m.dir);
const dirs = await fs.promises.readdir(projectsDir);
const matchingDirs: string[] = [];
for (const dir of dirs) {
const meta = JSON.parse(
await fs.promises.readFile(
path.join(projectsDir, dir, "meta.json"),
"utf8",
),
);
if (meta.rootDocumentId === collectionYId) {
matchingDirs.push(dir);
}
}
const combinedLog = [...creationLog, ...restartLog].join("\n");
@@ -1,6 +1,7 @@
import { MOCK_API_SERVER, SESSION_ID, test } from "@package/misc/fixtures";
import { expect } from "@playwright/test";
import { MOCK_API_SERVER, SESSION_ID, test } from "../../misc/fixtures";
test("Verify a new remote commit becomes pullable on a Cloud-synced workspace after a main-window focus change", async ({
user,
}) => {
@@ -1,15 +1,14 @@
import { faker } from "@faker-js/faker";
import { HttpMethod } from "@package/enums/http-method";
import { TreeNodeType } from "@package/enums/tree-node-types";
import { HTTP_SERVER, test } from "@package/misc/fixtures";
import type { Collection } from "@package/models/collection";
import { expect } from "@playwright/test";
import { HttpMethod } from "../../enums/http-method";
import { DEFAULT_TIMEOUT, HTTP_SERVER, test } from "../../misc/fixtures";
import type { Collection } from "../../models/collection";
test("Verify restoring an earlier snapshot removes a request added and committed after it on a Cloud-synced workspace", async ({
user,
}) => {
const { workspaceFlow, httpRequestFlow, cloudSyncFlow } = user.flowManager;
const { workspacePage } = user.pageManager;
await cloudSyncFlow.fetch("My Collection R1");
const collection = await workspaceFlow.getCollection({
@@ -21,18 +20,22 @@ test("Verify restoring an earlier snapshot removes a request added and committed
url: `${HTTP_SERVER}/get`,
method: HttpMethod.Get,
});
const addedNode = await workspacePage.requireItemNode(
{ name: addedName },
TreeNodeType.Request,
);
await cloudSyncFlow.commit(addedName, faker.lorem.sentence());
await cloudSyncFlow.restoreSnapshot("Initial Snapshot");
// The restore lands asynchronously and a reload before then aborts it, so
// wait for the live sidebar to reflect it before reloading.
await workspacePage.waitForNodeRemoved(addedNode);
await expect
.poll(() => httpRequestFlow.get(addedName, collection), {
timeout: DEFAULT_TIMEOUT,
})
.toBeUndefined();
await user.page.reload();
expect(await httpRequestFlow.get("New Request")).toBeDefined();
await workspacePage.waitForNodeRemoved(addedNode);
await expect
.poll(() => httpRequestFlow.get(addedName, collection), {
timeout: DEFAULT_TIMEOUT,
})
.toBeUndefined();
expect(await httpRequestFlow.get("New Request", collection)).toBeDefined();
});
@@ -1,9 +1,10 @@
import { faker } from "@faker-js/faker";
import { TreeNodeType } from "@package/enums/tree-node-types";
import { test } from "@package/misc/fixtures";
import { Collection } from "@package/models/collection";
import { expect } from "@playwright/test";
import { TreeNodeType } from "../../enums/tree-node-types";
import { test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
test("Verify dragging a request out of a Cloud Sync collection into an empty Cloud Sync collection", async ({
user,
}) => {
@@ -2,13 +2,12 @@ import * as fs from "node:fs";
import path from "node:path";
import { faker } from "@faker-js/faker";
import { readBackendProjectMetas } from "@package/misc/backend-projects";
import { getCloudSyncGraphQLRequestLog, setCloudSyncGraphQLDelay } from "@package/misc/cloud-sync-mock";
import { MOCK_API_SERVER, resetCloudSyncState, SESSION_ID, test } from "@package/misc/fixtures";
import { Collection } from "@package/models/collection";
import type { Environment } from "@package/models/environment";
import { expect } from "@playwright/test";
import { getCloudSyncGraphQLRequestLog, MOCK_API_SERVER, resetCloudSyncState, SESSION_ID, setCloudSyncGraphQLDelay, test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
import type { Environment } from "../../models/environment";
test.afterAll(async () => {
await setCloudSyncGraphQLDelay(0);
await fetch(`${MOCK_API_SERVER}/_admin/cloud-sync/new-commit`, {
@@ -36,10 +35,23 @@ test("Verify an unrelated action during collection creation is equally safe", as
const dataPath = await appFlow.getDataPath();
const projectsDir = path.join(dataPath, "version-control", "projects");
const readProjectMeta = async (workspaceId: string) => {
const [match] = (await readBackendProjectMetas(projectsDir))
.filter((m) => m.rootDocumentId === workspaceId)
.map((m) => ({ rootDocumentId: m.rootDocumentId, backendProjectId: m.id, dir: m.dir }));
return match;
const dirs = await fs.promises.readdir(projectsDir);
for (const dir of dirs) {
const meta = JSON.parse(
await fs.promises.readFile(
path.join(projectsDir, dir, "meta.json"),
"utf8",
),
);
if (meta.rootDocumentId === workspaceId) {
return {
rootDocumentId: meta.rootDocumentId as string,
backendProjectId: meta.id as string,
dir,
};
}
}
return;
};
const countBlobFiles = async (dir: string) => {
const blobsDir = path.join(projectsDir, dir, "blobs");
@@ -1,14 +1,15 @@
import { faker } from "@faker-js/faker";
import { test } from "@package/misc/fixtures";
import { Collection } from "@package/models/collection";
import { DialogDismissMethod } from "@package/pages/cloud-sync-file-delete.page";
import { expect } from "@playwright/test";
import { test } from "../../misc/fixtures";
import { Collection } from "../../models/collection";
import { DialogDismissMethod } from "../../pages/cloud-sync.page";
test("Verify the dashboard's unsynced-file delete button only appears on unsynced cards, is revealed on hover and keyboard focus, and cancelling its dialog leaves the file intact", async ({
user,
}) => {
const { workspaceFlow, cloudSyncFlow, appFlow } = user.flowManager;
const { cloudSyncPage, cloudSyncFileDeletePage } = user.pageManager;
const { cloudSyncPage } = user.pageManager;
const project = await workspaceFlow.getProject({ name: "Personal Workspace" });
const localCollection = await workspaceFlow.createCollection(
@@ -20,46 +21,46 @@ test("Verify the dashboard's unsynced-file delete button only appears on unsynce
await cloudSyncPage.backToAllProjects();
await cloudSyncFlow.openProjectDashboard(project!);
const syncedHasButton = await cloudSyncFileDeletePage.hasFileDeleteButton(
const syncedHasButton = await cloudSyncPage.hasFileDeleteButton(
"My Collection R1",
);
const localOnlyHasButton = await cloudSyncFileDeletePage.hasFileDeleteButton(
const localOnlyHasButton = await cloudSyncPage.hasFileDeleteButton(
(localCollection as Collection).name,
);
const unsyncedHasButton = await cloudSyncFileDeletePage.hasFileDeleteButton(
const unsyncedHasButton = await cloudSyncPage.hasFileDeleteButton(
"My Environment",
);
const revealedBeforeHover = await cloudSyncFileDeletePage.isFileDeleteButtonRevealed(
const revealedBeforeHover = await cloudSyncPage.isFileDeleteButtonRevealed(
"My Environment",
);
await cloudSyncFileDeletePage.hoverFile("My Environment");
const revealedOnHover = await cloudSyncFileDeletePage.isFileDeleteButtonRevealed(
await cloudSyncPage.hoverFile("My Environment");
const revealedOnHover = await cloudSyncPage.isFileDeleteButtonRevealed(
"My Environment",
);
await cloudSyncFileDeletePage.hoverFile((localCollection as Collection).name);
const revealedAfterUnhover = await cloudSyncFileDeletePage.isFileDeleteButtonRevealed(
await cloudSyncPage.hoverFile((localCollection as Collection).name);
const revealedAfterUnhover = await cloudSyncPage.isFileDeleteButtonRevealed(
"My Environment",
);
await cloudSyncFileDeletePage.focusFile("My Environment");
const revealedOnFocus = await cloudSyncFileDeletePage.isFileDeleteButtonRevealed(
await cloudSyncPage.focusFile("My Environment");
const revealedOnFocus = await cloudSyncPage.isFileDeleteButtonRevealed(
"My Environment",
);
const logLines = await appFlow.captureMainProcessLog(async () => {
await cloudSyncFileDeletePage.openFileDeleteDialog("My Environment");
await cloudSyncFileDeletePage.dismissFileDeleteDialog(DialogDismissMethod.XButton);
await cloudSyncFileDeletePage.openFileDeleteDialog("My Environment");
await cloudSyncFileDeletePage.dismissFileDeleteDialog(DialogDismissMethod.Escape);
await cloudSyncFileDeletePage.openFileDeleteDialog("My Environment");
await cloudSyncFileDeletePage.dismissFileDeleteDialog(
await cloudSyncPage.openFileDeleteDialog("My Environment");
await cloudSyncPage.dismissFileDeleteDialog(DialogDismissMethod.XButton);
await cloudSyncPage.openFileDeleteDialog("My Environment");
await cloudSyncPage.dismissFileDeleteDialog(DialogDismissMethod.Escape);
await cloudSyncPage.openFileDeleteDialog("My Environment");
await cloudSyncPage.dismissFileDeleteDialog(
DialogDismissMethod.ClickOutside,
);
});
const archivedLines = logLines.filter((line) =>
/Archived remote project/.test(line),
);
const fileStillPresent = await cloudSyncFileDeletePage.isFileCardVisible(
const fileStillPresent = await cloudSyncPage.isFileCardVisible(
"My Environment",
);
@@ -1,10 +1,11 @@
import { faker } from '@faker-js/faker';
import { ProjectType } from '@package/enums/project-types';
import { buildCollectionFile, GIT_CREDENTIAL, pushServerFile, test } from '@package/misc/git-fixtures';
import { Collection } from '@package/models/collection';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { ProjectType } from '../../enums/project-types';
import { buildCollectionFile, GIT_CREDENTIAL, pushServerFile, test } from '../../misc/git-fixtures';
import { Collection } from '../../models/collection';
import { Project } from '../../models/project';
test('Verify a branch that exists only on the remote is listed after Fetch and can be checked out', async ({ user }) => {
const { gitSyncFlow, preferencesFlow, workspaceFlow } = user.flowManager;
const { gitSyncPage, workspacePage } = user.pageManager;
@@ -1,12 +1,13 @@
import { faker } from '@faker-js/faker';
import { HttpMethod } from '@package/enums/http-method';
import { ProjectType } from '@package/enums/project-types';
import { HTTP_SERVER } from '@package/misc/fixtures';
import { getServerCommits, GIT_CREDENTIAL, test } from '@package/misc/git-fixtures';
import { Collection } from '@package/models/collection';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { HttpMethod } from '../../enums/http-method';
import { ProjectType } from '../../enums/project-types';
import { HTTP_SERVER } from '../../misc/fixtures';
import { getServerCommits, GIT_CREDENTIAL, test } from '../../misc/git-fixtures';
import { Collection } from '../../models/collection';
import { Project } from '../../models/project';
test('Verify committing a new collection and then a new request in a Git Sync project appears in History', async ({
user,
}) => {
@@ -1,15 +1,16 @@
import { faker } from '@faker-js/faker';
import { ProjectType } from '@package/enums/project-types';
import { GIT_CREDENTIAL, test } from '@package/misc/git-fixtures';
import { Collection } from '@package/models/collection';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { ProjectType } from '../../enums/project-types';
import { GIT_CREDENTIAL, test } from '../../misc/git-fixtures';
import { Collection } from '../../models/collection';
import { Project } from '../../models/project';
test('Verify committing with nothing staged prompts to stage all, and cancelling or omitting the message creates no commit', async ({
user,
}) => {
const { preferencesFlow, workspaceFlow } = user.flowManager;
const { gitSyncPage, gitCommitDialogPage, gitHistoryDialogPage } = user.pageManager;
const { gitSyncPage } = user.pageManager;
await preferencesFlow.addGitCredential(GIT_CREDENTIAL);
const project = await workspaceFlow.cloneGitProject(
@@ -20,23 +21,23 @@ test('Verify committing with nothing staged prompts to stage all, and cancelling
await workspaceFlow.createCollection(project, new Collection(faker.string.alphanumeric(10)), fileName);
await gitSyncPage.openHistory();
const initialCommit = await gitHistoryDialogPage.getLatestCommit();
await gitHistoryDialogPage.closeHistory();
const initialCommit = await gitSyncPage.getLatestCommit();
await gitSyncPage.closeHistory();
await gitSyncPage.openCommitDialog();
await gitCommitDialogPage.commitStaged(faker.string.alphanumeric(10));
const promptText = await gitCommitDialogPage.getStageAllPromptText();
await gitCommitDialogPage.cancelStageAllPrompt();
const stagedAfterCancel = await gitCommitDialogPage.getStagedChanges();
await gitCommitDialogPage.stageChange(`${fileName}.yaml`);
await gitCommitDialogPage.commitStaged();
const dialogOpenWithoutMessage = await gitCommitDialogPage.isCommitDialogOpen();
const stagedWithoutMessage = await gitCommitDialogPage.getStagedChanges();
await gitCommitDialogPage.closeCommitDialog();
await gitSyncPage.commitStaged(faker.string.alphanumeric(10));
const promptText = await gitSyncPage.getStageAllPromptText();
await gitSyncPage.cancelStageAllPrompt();
const stagedAfterCancel = await gitSyncPage.getStagedChanges();
await gitSyncPage.stageChange(`${fileName}.yaml`);
await gitSyncPage.commitStaged();
const dialogOpenWithoutMessage = await gitSyncPage.isCommitDialogOpen();
const stagedWithoutMessage = await gitSyncPage.getStagedChanges();
await gitSyncPage.closeCommitDialog();
await gitSyncPage.openHistory();
const latestCommit = await gitHistoryDialogPage.getLatestCommit();
await gitHistoryDialogPage.closeHistory();
const latestCommit = await gitSyncPage.getLatestCommit();
await gitSyncPage.closeHistory();
expect(promptText).toContain('There are no staged changes to commit');
expect(stagedAfterCancel).toEqual([]);
@@ -47,7 +48,7 @@ test('Verify committing with nothing staged prompts to stage all, and cancelling
test('Verify confirming the stage-all prompt stages every change and commits it', async ({ user }) => {
const { preferencesFlow, workspaceFlow } = user.flowManager;
const { gitSyncPage, gitCommitDialogPage, gitHistoryDialogPage } = user.pageManager;
const { gitSyncPage } = user.pageManager;
await preferencesFlow.addGitCredential(GIT_CREDENTIAL);
const project = await workspaceFlow.cloneGitProject(
@@ -67,11 +68,11 @@ test('Verify confirming the stage-all prompt stages every change and commits it'
const message = faker.string.alphanumeric(10);
await gitSyncPage.openCommitDialog();
await gitCommitDialogPage.commitStaged(message);
await gitCommitDialogPage.confirmStageAllPrompt();
await gitSyncPage.commitStaged(message);
await gitSyncPage.confirmStageAllPrompt();
await gitSyncPage.openHistory();
const latestCommit = await gitHistoryDialogPage.getLatestCommit();
await gitHistoryDialogPage.closeHistory();
const latestCommit = await gitSyncPage.getLatestCommit();
await gitSyncPage.closeHistory();
expect(latestCommit?.message).toBe(message);
});
@@ -1,9 +1,10 @@
import { faker } from '@faker-js/faker';
import { ProjectType } from '@package/enums/project-types';
import { getServerBranches, GIT_CREDENTIAL, test } from '@package/misc/git-fixtures';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { ProjectType } from '../../enums/project-types';
import { getServerBranches, GIT_CREDENTIAL, test } from '../../misc/git-fixtures';
import { Project } from '../../models/project';
test('Verify creating a new branch in a Git Sync project switches to it', async ({ user }) => {
const { gitSyncFlow, preferencesFlow, workspaceFlow } = user.flowManager;
const { gitSyncPage } = user.pageManager;
@@ -1,9 +1,10 @@
import { faker } from '@faker-js/faker';
import { ProjectType } from '@package/enums/project-types';
import { GIT_CREDENTIAL, test } from '@package/misc/git-fixtures';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { ProjectType } from '../../enums/project-types';
import { GIT_CREDENTIAL, test } from '../../misc/git-fixtures';
import { Project } from '../../models/project';
test('Verify creating a branch with a duplicate or invalid name shows an error and keeps the current branch', async ({
user,
}) => {
@@ -1,9 +1,10 @@
import { faker } from '@faker-js/faker';
import { ProjectType } from '@package/enums/project-types';
import { getServerBranches, GIT_CREDENTIAL, test } from '@package/misc/git-fixtures';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { ProjectType } from '../../enums/project-types';
import { getServerBranches, GIT_CREDENTIAL, test } from '../../misc/git-fixtures';
import { Project } from '../../models/project';
test(
'Verify deleting a branch checks out master and removes it from the list',
async ({ user }) => {
@@ -1,8 +1,9 @@
import { ProjectType } from "@package/enums/project-types";
import { DEFAULT_TIMEOUT, setGitSyncFeatureFlag } from "@package/misc/fixtures";
import { test } from "@package/misc/git-fixtures";
import { expect } from "@playwright/test";
import { ProjectType } from "../../enums/project-types";
import { DEFAULT_TIMEOUT, setGitSyncFeatureFlag } from "../../misc/fixtures";
import { test } from "../../misc/git-fixtures";
test.beforeAll(async () => {
await setGitSyncFeatureFlag(false);
});
@@ -14,10 +15,10 @@ test.afterAll(async () => {
test(
"Verify selecting the Git project type shows the disabled-feature banner, hides the setup form, and disables Scan for files when Git Sync is disabled by org feature flag",
async ({ user }) => {
const { workspacePage, projectSettingsPage, projectPage } = user.pageManager;
const { workspacePage, projectSettingsPage } = user.pageManager;
await projectPage.clickNewProject();
await projectPage.clickProjectTypeTile(ProjectType.Git);
await workspacePage.clickNewProject();
await workspacePage.clickProjectTypeTile(ProjectType.Git);
let bannerText: string | undefined;
await expect
@@ -30,11 +31,17 @@ test(
)
.toBeTruthy();
expect(bannerText).toContain("Git Sync limited to organizations");
await expect(projectSettingsPage.gitSetupForm).toBeHidden({
timeout: DEFAULT_TIMEOUT,
});
await expect(projectSettingsPage.scanForFilesButton).toBeDisabled({
timeout: DEFAULT_TIMEOUT,
});
await expect
.poll(() => projectSettingsPage.isHidden(projectSettingsPage.gitSetupForm), {
timeout: DEFAULT_TIMEOUT,
})
.toBe(true);
await expect
.poll(
() =>
projectSettingsPage.isDisabled(projectSettingsPage.scanForFilesButton),
{ timeout: DEFAULT_TIMEOUT },
)
.toBe(true);
},
);
@@ -1,8 +1,9 @@
import { ProjectType } from "@package/enums/project-types";
import { DEFAULT_TIMEOUT, setGitSyncStorageRule } from "@package/misc/fixtures";
import { test } from "@package/misc/git-fixtures";
import { expect } from "@playwright/test";
import { ProjectType } from "../../enums/project-types";
import { DEFAULT_TIMEOUT, setGitSyncStorageRule } from "../../misc/fixtures";
import { test } from "../../misc/git-fixtures";
test.beforeAll(async () => {
await setGitSyncStorageRule(false);
});
@@ -14,9 +15,9 @@ test.afterAll(async () => {
test(
"Verify the create-project dialog shows a storage-restriction banner and disables the Git project type when Git Sync is disabled by org storage rule",
async ({ user }) => {
const { workspacePage, projectSettingsPage, projectPage } = user.pageManager;
const { workspacePage, projectSettingsPage } = user.pageManager;
await projectPage.clickNewProject();
await workspacePage.clickNewProject();
let bannerText: string | undefined;
await expect
@@ -30,8 +31,14 @@ test(
.toBeTruthy();
expect(bannerText).toContain("Cloud Sync and Local Vault");
expect(bannerText).not.toContain("Git Sync");
await expect(
projectSettingsPage.projectTypeTile(ProjectType.Git),
).toBeDisabled({ timeout: DEFAULT_TIMEOUT });
await expect
.poll(
() =>
projectSettingsPage.isDisabled(
projectSettingsPage.projectTypeTile(ProjectType.Git),
),
{ timeout: DEFAULT_TIMEOUT },
)
.toBe(true);
},
);
@@ -1,10 +1,11 @@
import { faker } from "@faker-js/faker";
import { ProjectType } from "@package/enums/project-types";
import { getServerCommits, GIT_CREDENTIAL, test } from "@package/misc/git-fixtures";
import { Collection } from "@package/models/collection";
import { Project } from "@package/models/project";
import { expect } from "@playwright/test";
import { ProjectType } from "../../enums/project-types";
import { DEFAULT_TIMEOUT, getServerCommits, GIT_CREDENTIAL, test } from "../../misc/git-fixtures";
import { Collection } from "../../models/collection";
import { Project } from "../../models/project";
test(
"Verify discarding all unstaged changes removes the uncommitted collection and keeps the committed one",
async ({ user }) => {
@@ -28,10 +29,13 @@ test(
new Collection(faker.string.alphanumeric(10)),
faker.string.alphanumeric(10),
);
const collectionNode = await workspacePage.requireItemNode(collection);
await gitSyncFlow.discardAllChanges();
await workspacePage.waitForNodeRemoved(collectionNode);
await expect
.poll(async () => workspacePage.findItemNode(collection), {
timeout: DEFAULT_TIMEOUT,
})
.toBeUndefined();
const committedNode = await workspacePage.findItemNode(committedCollection);
const serverCommits = await getServerCommits();
@@ -1,10 +1,11 @@
import { faker } from '@faker-js/faker';
import { ProjectType } from '@package/enums/project-types';
import { getServerBranches, GIT_CREDENTIAL, test } from '@package/misc/git-fixtures';
import { Collection } from '@package/models/collection';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { ProjectType } from '../../enums/project-types';
import { getServerBranches, GIT_CREDENTIAL, test } from '../../misc/git-fixtures';
import { Collection } from '../../models/collection';
import { Project } from '../../models/project';
test(
'Verify merging a branch carries its committed changes into the current branch',
async ({ user }) => {
@@ -1,10 +1,11 @@
import { faker } from '@faker-js/faker';
import { ProjectType } from '@package/enums/project-types';
import { DEFAULT_TIMEOUT, GIT_CREDENTIAL, test } from '@package/misc/git-fixtures';
import { Collection } from '@package/models/collection';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { ProjectType } from '../../enums/project-types';
import { DEFAULT_TIMEOUT, GIT_CREDENTIAL, test } from '../../misc/git-fixtures';
import { Collection } from '../../models/collection';
import { Project } from '../../models/project';
for (const { version, chosenIndex } of [
{ version: 'Current', chosenIndex: 0 },
{ version: 'Incoming', chosenIndex: 1 },
@@ -3,11 +3,12 @@ import * as os from "node:os";
import path from "node:path";
import { faker } from "@faker-js/faker";
import { ProjectType } from "@package/enums/project-types";
import { test } from "@package/misc/git-fixtures";
import { Project } from "@package/models/project";
import { expect } from "@playwright/test";
import { ProjectType } from "../../enums/project-types";
import { test } from "../../misc/git-fixtures";
import { Project } from "../../models/project";
test(
"Verify opening a folder already adopted by another project shows a collision warning and disables Open",
async ({ user }) => {
@@ -3,11 +3,12 @@ import * as os from 'node:os';
import path from 'node:path';
import { faker } from '@faker-js/faker';
import { ProjectType } from '@package/enums/project-types';
import { test } from '@package/misc/git-fixtures';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { ProjectType } from '../../enums/project-types';
import { test } from '../../misc/git-fixtures';
import { Project } from '../../models/project';
test(
'Verify adopting an existing local folder as a Git project runs git init in it',
async ({ user }) => {
@@ -1,53 +1,44 @@
import { faker } from '@faker-js/faker';
import { ProjectType } from '@package/enums/project-types';
import type { UserSession } from '@package/misc/fixtures';
import { buildCollectionFile, DEFAULT_TIMEOUT, GIT_CREDENTIAL, pushServerFile, test } from '@package/misc/git-fixtures';
import { Collection } from '@package/models/collection';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
async function pullIntoConflict(user: UserSession, version: 'Current' | 'Incoming') {
const { gitSyncFlow, preferencesFlow, workspaceFlow } = user.flowManager;
const { gitBranchesDialogPage } = user.pageManager;
import { ProjectType } from '../../enums/project-types';
import { buildCollectionFile, DEFAULT_TIMEOUT, GIT_CREDENTIAL, pushServerFile, test } from '../../misc/git-fixtures';
import { Collection } from '../../models/collection';
import { Project } from '../../models/project';
await preferencesFlow.addGitCredential(GIT_CREDENTIAL);
const project = await workspaceFlow.cloneGitProject(
new Project(faker.string.alphanumeric(10), ProjectType.Git),
GIT_CREDENTIAL.name,
);
const fileName = faker.string.alpha(10).toLowerCase();
const collection = await workspaceFlow.createCollection(project, new Collection(faker.string.alphanumeric(10)), fileName);
await gitSyncFlow.commitAndPush(faker.string.alphanumeric(10));
for (const { version, keepsLocal } of [
{ version: 'Current', keepsLocal: true },
{ version: 'Incoming', keepsLocal: false },
] as const) {
test(`Verify resolving a pull conflict by choosing the ${version} version keeps that side's collection name`, async ({
user,
}) => {
const { gitSyncFlow, preferencesFlow, workspaceFlow } = user.flowManager;
const { gitSyncPage, workspacePage } = user.pageManager;
const localCollection = new Collection(faker.string.alpha(10));
const remoteCollection = new Collection(faker.string.alpha(10));
await workspaceFlow.renameCollection(collection, localCollection.name);
await gitSyncFlow.commit(faker.string.alphanumeric(10));
await pushServerFile({ path: `${fileName}.yaml`, content: buildCollectionFile(remoteCollection.name) });
await gitSyncFlow.pull();
await gitBranchesDialogPage.resolveMergeConflicts(version);
return { localCollection, remoteCollection };
await preferencesFlow.addGitCredential(GIT_CREDENTIAL);
const project = await workspaceFlow.cloneGitProject(
new Project(faker.string.alphanumeric(10), ProjectType.Git),
GIT_CREDENTIAL.name,
);
const fileName = faker.string.alpha(10).toLowerCase();
const collection = await workspaceFlow.createCollection(project, new Collection(faker.string.alphanumeric(10)), fileName);
await gitSyncFlow.commitAndPush(faker.string.alphanumeric(10));
const localCollection = new Collection(faker.string.alpha(10));
const remoteCollection = new Collection(faker.string.alpha(10));
await workspaceFlow.renameCollection(collection, localCollection.name);
await gitSyncFlow.commit(faker.string.alphanumeric(10));
await pushServerFile({ path: `${fileName}.yaml`, content: buildCollectionFile(remoteCollection.name) });
await gitSyncFlow.pull();
await gitSyncPage.resolveMergeConflicts(version);
const keptCollection = keepsLocal ? localCollection : remoteCollection;
await expect
.poll(async () => workspacePage.findItemNode(keptCollection), { timeout: DEFAULT_TIMEOUT })
.toBeDefined();
if (keepsLocal) {
expect(await workspacePage.findItemNode(remoteCollection)).toBeUndefined();
}
});
}
test("Verify resolving a pull conflict by choosing the Current version keeps that side's collection name", async ({
user,
}) => {
const { workspacePage } = user.pageManager;
const { localCollection, remoteCollection } = await pullIntoConflict(user, 'Current');
await expect
.poll(async () => workspacePage.findItemNode(localCollection), { timeout: DEFAULT_TIMEOUT })
.toBeDefined();
expect(await workspacePage.findItemNode(remoteCollection)).toBeUndefined();
});
test("Verify resolving a pull conflict by choosing the Incoming version keeps that side's collection name", async ({
user,
}) => {
const { workspacePage } = user.pageManager;
const { remoteCollection } = await pullIntoConflict(user, 'Incoming');
await expect
.poll(async () => workspacePage.findItemNode(remoteCollection), { timeout: DEFAULT_TIMEOUT })
.toBeDefined();
});
@@ -1,11 +1,12 @@
import { faker } from '@faker-js/faker';
import { ProjectType } from '@package/enums/project-types';
import { DEFAULT_TIMEOUT, GIT_CREDENTIAL, pushServerFile, test } from '@package/misc/git-fixtures';
import { buildCollectionFile } from '@package/misc/git-fixtures';
import { Collection } from '@package/models/collection';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { ProjectType } from '../../enums/project-types';
import { DEFAULT_TIMEOUT, GIT_CREDENTIAL, pushServerFile, test } from '../../misc/git-fixtures';
import { buildCollectionFile } from '../../misc/git-fixtures';
import { Collection } from '../../models/collection';
import { Project } from '../../models/project';
test('Verify pulling brings in a collection that was pushed to the remote by another client', async ({ user }) => {
const { gitSyncFlow, preferencesFlow, workspaceFlow } = user.flowManager;
const { workspacePage } = user.pageManager;
@@ -1,10 +1,11 @@
import { faker } from '@faker-js/faker';
import { ProjectType } from '@package/enums/project-types';
import { getServerBranches, getServerCommits, GIT_CREDENTIAL, test } from '@package/misc/git-fixtures';
import { Collection } from '@package/models/collection';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { ProjectType } from '../../enums/project-types';
import { getServerBranches, getServerCommits, GIT_CREDENTIAL, test } from '../../misc/git-fixtures';
import { Collection } from '../../models/collection';
import { Project } from '../../models/project';
test('Verify pushing committed changes to the remote succeeds', async ({ user }) => {
const { gitSyncFlow, preferencesFlow, workspaceFlow } = user.flowManager;
@@ -3,12 +3,13 @@ import * as os from 'node:os';
import path from 'node:path';
import { faker } from '@faker-js/faker';
import { ProjectType } from '@package/enums/project-types';
import { DEFAULT_TIMEOUT } from '@package/misc/fixtures';
import { GIT_CREDENTIAL, test } from '@package/misc/git-fixtures';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { ProjectType } from '../../enums/project-types';
import { DEFAULT_TIMEOUT } from '../../misc/fixtures';
import { GIT_CREDENTIAL, test } from '../../misc/git-fixtures';
import { Project } from '../../models/project';
test(
"Verify relocating a Git Sync project's repository shows an error when the destination folder already exists",
async ({ user }) => {
@@ -23,8 +24,7 @@ test(
await workspaceFlow.openSettings(project);
const destinationDir = fs.mkdtempSync(path.join(os.tmpdir(), 'insomnia-relocate-collision-'));
fs.writeFileSync(path.join(destinationDir, 'existing-file.txt'), '');
await user.flowManager.context.stubFileChooser(destinationDir);
await projectSettingsPage.relocateRepository();
await projectSettingsPage.relocateRepository(destinationDir);
await expect
.poll(() => projectSettingsPage.getRelocationError(), {
@@ -3,12 +3,13 @@ import * as os from "node:os";
import path from "node:path";
import { faker } from "@faker-js/faker";
import { ProjectType } from "@package/enums/project-types";
import { DEFAULT_TIMEOUT } from "@package/misc/fixtures";
import { GIT_CREDENTIAL, test } from "@package/misc/git-fixtures";
import { Project } from "@package/models/project";
import { expect } from "@playwright/test";
import { ProjectType } from "../../enums/project-types";
import { DEFAULT_TIMEOUT } from "../../misc/fixtures";
import { GIT_CREDENTIAL, test } from "../../misc/git-fixtures";
import { Project } from "../../models/project";
test(
"Verify relocating a Git Sync project's repository to a new folder updates the displayed path",
async ({ user }) => {
@@ -24,8 +25,7 @@ test(
const destinationDir = fs.mkdtempSync(
path.join(os.tmpdir(), "insomnia-relocate-dest-"),
);
await user.flowManager.context.stubFileChooser(destinationDir);
await projectSettingsPage.relocateRepository();
await projectSettingsPage.relocateRepository(destinationDir);
await expect
.poll(() => projectSettingsPage.getRepositoryPath(), {
@@ -1,13 +1,14 @@
import { faker } from '@faker-js/faker';
import { HttpMethod } from '@package/enums/http-method';
import { ProjectType } from '@package/enums/project-types';
import { TreeNodeType } from '@package/enums/tree-node-types';
import { HTTP_SERVER } from '@package/misc/fixtures';
import { GIT_CREDENTIAL, test } from '@package/misc/git-fixtures';
import { Collection } from '@package/models/collection';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { HttpMethod } from '../../enums/http-method';
import { ProjectType } from '../../enums/project-types';
import { TreeNodeType } from '../../enums/tree-node-types';
import { HTTP_SERVER } from '../../misc/fixtures';
import { GIT_CREDENTIAL, test } from '../../misc/git-fixtures';
import { Collection } from '../../models/collection';
import { Project } from '../../models/project';
test(
'Verify dragging a request out of a Git Sync collection into an empty Git Sync collection',
async ({ user }) => {
@@ -1,10 +1,11 @@
import { faker } from '@faker-js/faker';
import { ProjectType } from '@package/enums/project-types';
import { GIT_CREDENTIAL, test } from '@package/misc/git-fixtures';
import { Collection } from '@package/models/collection';
import { Project } from '@package/models/project';
import { expect } from "@playwright/test";
import { ProjectType } from '../../enums/project-types';
import { GIT_CREDENTIAL, test } from '../../misc/git-fixtures';
import { Collection } from '../../models/collection';
import { Project } from '../../models/project';
test('Verify staging and unstaging individual changes only commits the staged file', async ({ user }) => {
const { gitSyncFlow, preferencesFlow, workspaceFlow } = user.flowManager;
@@ -1,7 +1,8 @@
import { faker } from '@faker-js/faker';
import { DEFAULT_TIMEOUT, setKonnectEntitlement, test } from '@package/misc/fixtures';
import { expect } from "@playwright/test";
import { DEFAULT_TIMEOUT, setKonnectEntitlement, test } from '../../misc/fixtures';
test.beforeAll(async () => {
await setKonnectEntitlement(true);
});
@@ -13,9 +14,9 @@ test.afterAll(async () => {
test(
'Verify configuring a Konnect PAT closes the settings modal, shows the Sync Konnect button, and leaves the Projects tab usable',
async ({ user }) => {
const { konnectPage, workspacePage, projectPage } = user.pageManager;
const { konnectPage, workspacePage } = user.pageManager;
await projectPage.switchOrganization('Control Planes');
await workspacePage.switchOrganization('Control Planes');
await konnectPage.openTab();
const introVisibleBeforeConfigure = await konnectPage.isIntroCardVisible();
await konnectPage.clickConfigure();
@@ -38,9 +39,9 @@ test(
test(
'Verify the initial Konnect sync creates the control plane project and a collection for each gateway service',
async ({ user }) => {
const { konnectPage, workspacePage, projectPage } = user.pageManager;
const { konnectPage, workspacePage } = user.pageManager;
await projectPage.switchOrganization('Control Planes');
await workspacePage.switchOrganization('Control Planes');
await konnectPage.openTab();
await konnectPage.clickConfigure();
await konnectPage.setPat(`kpat_${faker.string.alphanumeric(20)}`);
@@ -68,9 +69,9 @@ test(
test(
'Verify syncing Konnect again after the first sync completes without errors',
async ({ user }) => {
const { konnectPage, workspacePage, projectPage } = user.pageManager;
const { konnectPage, workspacePage } = user.pageManager;
await projectPage.switchOrganization('Control Planes');
await workspacePage.switchOrganization('Control Planes');
await konnectPage.openTab();
await konnectPage.clickConfigure();
await konnectPage.setPat(`kpat_${faker.string.alphanumeric(20)}`);
@@ -1,6 +1,7 @@
import { setKonnectEntitlement, test } from "@package/misc/fixtures";
import { expect } from "@playwright/test";
import { setKonnectEntitlement, test } from "../../misc/fixtures";
test.afterEach(async () => {
await setKonnectEntitlement(false);
});
@@ -1,8 +1,9 @@
import { faker } from "@faker-js/faker";
import { CollaboratorRole } from "@package/enums/collaborator-role";
import { test } from "@package/misc/fixtures";
import { expect } from "@playwright/test";
import { CollaboratorRole } from "../../enums/collaborator-role";
import { test } from "../../misc/fixtures";
test("Verify inviting collaborators by email and org member and changing a member's role", async ({
user,
}) => {