Skip to content

Instantly share code, notes, and snippets.

@tgriesser
Created April 30, 2025 17:33
Show Gist options
  • Save tgriesser/7ccc598f95abadf064d954581b71a424 to your computer and use it in GitHub Desktop.
Save tgriesser/7ccc598f95abadf064d954581b71a424 to your computer and use it in GitHub Desktop.
Generated Code
import {
test,
expect,
chromium,
Browser,
type CDPSession,
type TestInfo,
type Locator,
type Page,
type Response,
type BrowserContext,
} from "@playwright/test";
import fs from "fs";
import os from "os";
import path from "path";
import fsPromises from "fs/promises";
import { Readable } from "stream";
import { finished } from "stream/promises";
import { URL } from "url";
declare global {
interface Window {
octomindWrittenValueTracker?: WeakMap<HTMLElement, string>;
}
}
const handleHydrationIssues = async (
enterTextLocator: Locator,
textToEnter: string,
timeout: number
): Promise<void> => {
await enterTextLocator.evaluate(
(inputElement: HTMLElement, textToEnter: string) => {
if (!window.octomindWrittenValueTracker) {
window.octomindWrittenValueTracker = new WeakMap<HTMLElement, string>();
}
window.octomindWrittenValueTracker.set(inputElement, textToEnter);
if (!("value" in inputElement)) {
return;
}
const hydrationObserverStartedAttribute = "octo-hydration-watcher";
if (inputElement.getAttribute(hydrationObserverStartedAttribute)) {
// no need to register a second observer, we already interacted with this element
// and it was not reloaded
return;
}
inputElement.setAttribute(hydrationObserverStartedAttribute, "true");
const defaultValue =
"defaultValue" in inputElement
? (inputElement.defaultValue as string)
: "";
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (
mutation.type === "attributes" &&
mutation.attributeName === "value"
) {
if (
"value" in mutation.target &&
mutation.target.value === defaultValue
) {
const lastEnteredValue =
window.octomindWrittenValueTracker?.get(inputElement);
if (lastEnteredValue !== undefined) {
inputElement.setAttribute("value", lastEnteredValue);
inputElement.value = lastEnteredValue;
}
observer.disconnect();
inputElement.removeAttribute(hydrationObserverStartedAttribute);
window.octomindWrittenValueTracker?.delete(inputElement);
}
}
}
});
observer.observe(inputElement, {
attributes: true,
attributeFilter: ["value"],
});
},
textToEnter,
{ timeout }
);
};
const innerDownloadFile = async (url: string): Promise<string | undefined> => {
const parsedUrl = new URL(url);
const lastPathElement = parsedUrl.pathname.split("/").pop();
const dir = await fsPromises.mkdtemp(path.join(os.tmpdir(), "download-"));
const destination = path.join(
dir,
lastPathElement ? lastPathElement : "file"
);
let status = 0;
try {
const res = await fetch(url);
status = res.status;
if (res.ok && res.body) {
const fileStream = fs.createWriteStream(destination, { flags: "wx" });
await finished(Readable.fromWeb(res.body as any).pipe(fileStream));
return destination;
}
} catch (e) {
console.error({ url, e }, "download failed");
}
throw new Error(`download failed: status: ${status}`);
};
type Callback = (...args: any[]) => Promise<string | undefined>;
const retryWithExponentialBackoff = async (
fn: Callback,
maxAttempts = 5,
baseDelayMs = 1000
) => {
let attempt = 1;
const execute = async () => {
try {
return await fn();
} catch (error) {
if (attempt >= maxAttempts) {
throw error;
}
const delayMs = baseDelayMs * 2 ** attempt;
console.log(`${error}: Retry attempt ${attempt} after ${delayMs}ms`);
await new Promise((resolve) => setTimeout(resolve, delayMs));
attempt++;
return execute();
}
};
return execute();
};
const downloadFile = async (url: string): Promise<string | undefined> => {
return retryWithExponentialBackoff(async () => innerDownloadFile(url));
};
const saveScrollIntoView = async ({
locator,
timeout,
}: {
locator: Locator;
timeout: number;
}): Promise<void> => {
try {
await locator.scrollIntoViewIfNeeded({ timeout });
} catch (error) {
//scrolling can fail if e.g. the assertion is "NOT_VISIBLE" but we don't actually care and want to continue
console.error({ error }, "Could not scroll into view, ignoring");
}
};
const attachBoundingBox = (locator: Locator, timeout: number): Promise<void> =>
Promise.resolve();
const utilizeTimeBudget = async (
budgetMs: number,
callback: () => Promise<void>
): Promise<number> => {
const start = performance.now();
await callback();
const end = performance.now();
const duration = end - start;
const leftOver = budgetMs - duration;
return Math.max(leftOver, 1000);
};
const testStartTime = new Date();
const generateEmailUrl = ({
subjectContaining,
}: {
subjectContaining: string | undefined;
}): string => {
let emailReceivedAfter = new Date(testStartTime.getTime() - 5000);
let url =
"https://app.octomind.dev/signed/testtargets/52d6e23c-bf4b-44e7-bb2d-ca08f006e699/environments/3a3f31af-6d83-4607-9546-9ae7896a5134/after/" +
emailReceivedAfter.toISOString() +
"/email?signature=" +
"AAAAAGgSevkR7SZQ5-IE9rprwvVcMmfgFwzqxdpmuKQgY2UoFzhfMg";
if (subjectContaining) {
url += `&subject=${encodeURIComponent(subjectContaining)}`;
}
return url.toString();
};
test.describe("octomind-generated", () => {
const dynamicVariables: Map<string, string> = new Map();
const setDynamicVariable = (name: string, value: string): void => {
const stringToMatch = `$$${name}`;
if (
/\$\$(?!OCTO)([a-zA-Z][a-zA-Z0-9_]*)/.exec(stringToMatch)?.[0] !==
stringToMatch
) {
throw new Error(
`Variable name must be alphanumeric with underscores and start with a letter, but was: ${name}`
);
}
dynamicVariables.set(name, value);
};
const getDynamicVariable = (name: string): string => {
if (!dynamicVariables.has(name)) {
throw new Error(`Variable not set: ${name}`);
}
return dynamicVariables.get(name) as string;
};
const getClipboardContent = async (page: Page): Promise<string> => {
return page.evaluate(() => navigator.clipboard.readText());
};
const getShortStableId = ({ retry }: { retry: number }): string => {
const idsByRetry = ["0ae83f2c", "331cd011"];
return (
idsByRetry[retry] ??
`<no (\$OCTO_STABLE_UUID_SHORT) generated for retry ${retry}>`
);
};
const getStableId = ({ retry }: { retry: number }): string => {
const idsByRetry = [
"c005861e-2f46-4be3-8a4a-eceaa5a6fc21",
"7047380c-96a2-4d5c-bbb2-5a74e2d4466d",
];
return (
idsByRetry[retry] ??
`<no (\$OCTO_STABLE_UUID) generated for retry ${retry}>`
);
};
let activePage: Page;
let newPage: Page | undefined;
test.beforeEach(async ({ context, page }) => {
await page.addInitScript(() => {
// prevent links from opening new tabs
const callback = (): void => {
const elements = document.querySelectorAll("a[target=_blank]");
for (const element of elements) {
element.removeAttribute("target");
}
};
const observer = new window.MutationObserver(callback);
observer.observe(document, { childList: true, subtree: true });
});
newPage = undefined;
context.on("page", async (newlyOpenedPage) => {
newPage = newlyOpenedPage;
await newlyOpenedPage.addInitScript(() => {
// prevent links from opening new tabs
const callback = (): void => {
const elements = document.querySelectorAll("a[target=_blank]");
for (const element of elements) {
element.removeAttribute("target");
}
};
const observer = new window.MutationObserver(callback);
observer.observe(document, { childList: true, subtree: true });
});
});
activePage = page;
});
// eslint-disable-next-line no-empty-pattern
test.afterEach(({}, testInfo) => {
console.log(
`Finished test with status: [${testInfo.title}: ${String(
testInfo.status
)}]`
);
});
test.afterAll(
"Close all browser contexts",
async ({ browser }, _testInfo) => {
// try to more reliably get trace files, especially in timeout error cases
// https://github.com/microsoft/playwright/issues/14773#issuecomment-1178108058
console.log("Closing all browser contexts");
for (const context of browser.contexts()) {
await context.close();
}
}
);
test.describe("docs billing flow - 39e49e29-8191-4ee6-bd74-fb811b558a6d - describe", () => {
const filterSteps = <T extends { type: "LOGIN" | "COOKIE_BANNER" | null }>(
steps: T[],
typesToSkip: Set<"LOGIN" | "COOKIE_BANNER" | null>
): (T & { type: "LOGIN" | "COOKIE_BANNER" | null })[] => {
if (!fs.existsSync(".auth/state.json")) {
return steps;
}
const isSkippableStep = (e: {
type: "LOGIN" | "COOKIE_BANNER" | null;
}): boolean => typesToSkip.has(e.type);
const firstIndexSkipped = steps.findIndex(isSkippableStep);
if (firstIndexSkipped === -1) {
return steps;
} else {
const lastIndexSkipped = steps.findLastIndex(isSkippableStep);
return [
...steps.slice(0, firstIndexSkipped),
{
fn: async (page: Page): Promise<void> => {
const savedAfterLoginUrl = (
JSON.parse(
await fsPromises.readFile(".auth/state.json", {
encoding: "utf8",
})
) as { octomind: { urlAfterLogin: string } }
).octomind.urlAfterLogin;
await page
.context()
.tracing.group("1. Go to URL", {
location: {
file: '#octo: {"elementId":"generated-for-navigation","ignoreFailure":false,"plannedIndex":0,"actionOrExpectation":"GO_TO"}',
line: 1,
column: 1,
},
});
try {
await page.goto(savedAfterLoginUrl, { timeout: 30000 });
} finally {
await page.context().tracing.groupEnd();
}
},
type: null,
retryable: false,
ignoreFailure: false,
},
...steps.slice(lastIndexSkipped + 1),
];
}
};
test("docs billing flow - 39e49e29-8191-4ee6-bd74-fb811b558a6d", async ({
context,
}, testInfo) => {
const step0 = {
fn: async (page: Page): Promise<void> => {
await page
.context()
.tracing.group("1. navigate to URL", {
location: {
file: '#octo: {"elementId":"generated-for-navigation","ignoreFailure":false,"plannedIndex":0,"actionOrExpectation":"GO_TO"}',
line: 1,
column: 1,
},
});
try {
let timeBudget = 30000;
await utilizeTimeBudget(timeBudget, async () => {
await page.goto("https://www.netlify.com/", {
timeout: timeBudget,
});
await page.waitForLoadState();
});
} finally {
await page.context().tracing.groupEnd();
}
},
type: null,
retryable: true,
ignoreFailure: false,
} as const;
const step1 = {
fn: async (page: Page): Promise<void> => {
await page
.context()
.tracing.group("2. left-click on 'Docs' link", {
location: {
file: '#octo: {"elementId":"9f5b28cf-7ff1-4b9b-9c9c-d7c4995031f5","ignoreFailure":false,"plannedIndex":1,"actionOrExpectation":"CLICK"}',
line: 1,
column: 1,
},
});
try {
const locator1 = page
.getByLabel("Site navigation")
.getByRole("link", { name: "Docs" })
.locator("visible=true");
let timeBudget = 30000;
timeBudget = await utilizeTimeBudget(
timeBudget,
async () =>
await saveScrollIntoView({
locator: locator1,
timeout: timeBudget,
})
);
timeBudget = await utilizeTimeBudget(timeBudget, async () =>
attachBoundingBox({
locator: locator1,
timeout: timeBudget,
attachmentName:
"boundingrectprefixdivider9f5b28cf-7ff1-4b9b-9c9c-d7c4995031f5timingdividerbefore",
testInfo: testInfo,
})
);
await utilizeTimeBudget(timeBudget, async () => {
await locator1.click({ timeout: timeBudget, ...{} });
await page.waitForLoadState();
});
} finally {
await page.context().tracing.groupEnd();
}
},
type: null,
retryable: true,
ignoreFailure: false,
} as const;
const step2 = {
fn: async (page: Page): Promise<void> => {
await page
.context()
.tracing.group("3. left-click on 'search input' combobox", {
location: {
file: '#octo: {"elementId":"872794e7-75cd-4b6c-b54e-8f039de276da","ignoreFailure":false,"plannedIndex":2,"actionOrExpectation":"CLICK"}',
line: 1,
column: 1,
},
});
try {
const locator2 = page
.getByRole("combobox", { name: "search input" })
.locator("visible=true");
let timeBudget = 30000;
timeBudget = await utilizeTimeBudget(
timeBudget,
async () =>
await saveScrollIntoView({
locator: locator2,
timeout: timeBudget,
})
);
timeBudget = await utilizeTimeBudget(timeBudget, async () =>
attachBoundingBox({
locator: locator2,
timeout: timeBudget,
attachmentName:
"boundingrectprefixdivider872794e7-75cd-4b6c-b54e-8f039de276datimingdividerbefore",
testInfo: testInfo,
})
);
await utilizeTimeBudget(timeBudget, async () => {
await locator2.click({ timeout: timeBudget, ...{} });
await page.waitForLoadState();
});
} finally {
await page.context().tracing.groupEnd();
}
},
type: null,
retryable: true,
ignoreFailure: false,
} as const;
const step3 = {
fn: async (page: Page): Promise<void> => {
await page
.context()
.tracing.group(
"4. enter 'Billing FAQ' into 'search input' combobox",
{
location: {
file: '#octo: {"elementId":"d7b6fceb-66cb-4fab-819c-0b83a8283342","ignoreFailure":false,"plannedIndex":3,"actionOrExpectation":"ENTER_TEXT"}',
line: 1,
column: 1,
},
}
);
try {
const locator3 = page
.getByRole("combobox", { name: "search input" })
.locator("visible=true");
let timeBudget = 30000;
timeBudget = await utilizeTimeBudget(
timeBudget,
async () =>
await saveScrollIntoView({
locator: locator3,
timeout: timeBudget,
})
);
timeBudget = await utilizeTimeBudget(timeBudget, async () =>
attachBoundingBox({
locator: locator3,
timeout: timeBudget,
attachmentName:
"boundingrectprefixdividerd7b6fceb-66cb-4fab-819c-0b83a8283342timingdividerbefore",
testInfo: testInfo,
})
);
await utilizeTimeBudget(timeBudget, async () => {
timeBudget = await utilizeTimeBudget(timeBudget, async () =>
handleHydrationIssues(locator3, "Billing FAQ", timeBudget)
);
await locator3.fill("Billing FAQ", { timeout: timeBudget });
await page.waitForLoadState();
});
} finally {
await page.context().tracing.groupEnd();
}
},
type: null,
retryable: true,
ignoreFailure: false,
} as const;
const step4 = {
fn: async (page: Page): Promise<void> => {
await page
.context()
.tracing.group("5. press key Enter on 'search input' combobox", {
location: {
file: '#octo: {"elementId":"0af58d9d-7196-4b8f-84a2-a946eebbdd74","ignoreFailure":false,"plannedIndex":4,"actionOrExpectation":"KEY_PRESS"}',
line: 1,
column: 1,
},
});
try {
const locator4 = page
.getByRole("combobox", { name: "search input" })
.locator("visible=true");
let timeBudget = 30000;
timeBudget = await utilizeTimeBudget(
timeBudget,
async () =>
await saveScrollIntoView({
locator: locator4,
timeout: timeBudget,
})
);
timeBudget = await utilizeTimeBudget(timeBudget, async () =>
attachBoundingBox({
locator: locator4,
timeout: timeBudget,
attachmentName:
"boundingrectprefixdivider0af58d9d-7196-4b8f-84a2-a946eebbdd74timingdividerbefore",
testInfo: testInfo,
})
);
await utilizeTimeBudget(timeBudget, async () => {
await locator4.click({ timeout: timeBudget });
await locator4.press("Enter", { timeout: timeBudget });
await page.waitForLoadState();
});
} finally {
await page.context().tracing.groupEnd();
}
},
type: null,
retryable: true,
ignoreFailure: false,
} as const;
const step5 = {
fn: async (page: Page): Promise<void> => {
await page
.context()
.tracing.group(
"6. left-click on 'What’s an extra usage package?' link",
{
location: {
file: '#octo: {"elementId":"e9c0f744-4801-4d5d-86d1-5c9ea2b628e6","ignoreFailure":false,"plannedIndex":5,"actionOrExpectation":"CLICK"}',
line: 1,
column: 1,
},
}
);
try {
const locator5 = page
.getByRole("link", { name: "What’s an extra usage package?" })
.locator("visible=true");
let timeBudget = 30000;
timeBudget = await utilizeTimeBudget(
timeBudget,
async () =>
await saveScrollIntoView({
locator: locator5,
timeout: timeBudget,
})
);
timeBudget = await utilizeTimeBudget(timeBudget, async () =>
attachBoundingBox({
locator: locator5,
timeout: timeBudget,
attachmentName:
"boundingrectprefixdividere9c0f744-4801-4d5d-86d1-5c9ea2b628e6timingdividerbefore",
testInfo: testInfo,
})
);
await utilizeTimeBudget(timeBudget, async () => {
await locator5.click({ timeout: timeBudget, ...{} });
await page.waitForLoadState();
});
} finally {
await page.context().tracing.groupEnd();
}
},
type: null,
retryable: true,
ignoreFailure: false,
} as const;
const step6 = {
fn: async (page: Page): Promise<void> => {
await page
.context()
.tracing.group(
"7. left-click on filtered by hasText: 'With metered features on paid' link",
{
location: {
file: '#octo: {"elementId":"c735c10f-beeb-48ec-bbae-bf4b2c176930","ignoreFailure":false,"plannedIndex":6,"actionOrExpectation":"CLICK"}',
line: 1,
column: 1,
},
}
);
try {
const scrollLocator6 = page.locator("html");
try {
await scrollLocator6.evaluate(
(e) => (e.scrollTop = 1836),
undefined,
{ timeout: 30000 }
);
} catch (e) {
//scrolling can fail if e.g. the assertion is "NOT_VISIBLE" but we don't actually care and want to continue
}
const locator6 = page
.getByRole("paragraph")
.filter({ hasText: "With metered features on paid" })
.getByRole("link")
.locator("visible=true");
let timeBudget = 30000;
timeBudget = await utilizeTimeBudget(
timeBudget,
async () =>
await saveScrollIntoView({
locator: locator6,
timeout: timeBudget,
})
);
timeBudget = await utilizeTimeBudget(timeBudget, async () =>
attachBoundingBox({
locator: locator6,
timeout: timeBudget,
attachmentName:
"boundingrectprefixdividerc735c10f-beeb-48ec-bbae-bf4b2c176930timingdividerbefore",
testInfo: testInfo,
})
);
await utilizeTimeBudget(timeBudget, async () => {
await locator6.click({ timeout: timeBudget, ...{} });
await page.waitForLoadState();
});
} finally {
await page.context().tracing.groupEnd();
}
},
type: null,
retryable: true,
ignoreFailure: false,
} as const;
const step7 = {
fn: async (page: Page): Promise<void> => {
await page
.context()
.tracing.group("8. left-click on 'Sign up' link", {
location: {
file: '#octo: {"elementId":"23d9d173-3373-4b3d-a653-f438f08171d8","ignoreFailure":false,"plannedIndex":7,"actionOrExpectation":"CLICK"}',
line: 1,
column: 1,
},
});
try {
const locator7 = page
.getByRole("link", { name: "Sign up" })
.locator("visible=true");
let timeBudget = 30000;
timeBudget = await utilizeTimeBudget(
timeBudget,
async () =>
await saveScrollIntoView({
locator: locator7,
timeout: timeBudget,
})
);
timeBudget = await utilizeTimeBudget(timeBudget, async () =>
attachBoundingBox({
locator: locator7,
timeout: timeBudget,
attachmentName:
"boundingrectprefixdivider23d9d173-3373-4b3d-a653-f438f08171d8timingdividerbefore",
testInfo: testInfo,
})
);
await utilizeTimeBudget(timeBudget, async () => {
await locator7.click({ timeout: timeBudget, ...{} });
await page.waitForLoadState();
});
} finally {
await page.context().tracing.groupEnd();
}
},
type: null,
retryable: true,
ignoreFailure: false,
} as const;
const step8 = {
fn: async (page: Page): Promise<void> => {
await page
.context()
.tracing.group(
"9. check that 'Sign up with email' button is visible",
{
location: {
file: '#octo: {"elementId":"492a43fa-3dac-4506-a3d4-c218f381bd17","ignoreFailure":false,"plannedIndex":8,"actionOrExpectation":"VISIBLE"}',
line: 1,
column: 1,
},
}
);
try {
const locator8 = page
.getByRole("button", { name: "Sign up with email" })
.locator("visible=true");
let timeBudget = 30000;
timeBudget = await utilizeTimeBudget(
timeBudget,
async () =>
await saveScrollIntoView({
locator: locator8,
timeout: timeBudget,
})
);
timeBudget = await utilizeTimeBudget(timeBudget, async () =>
attachBoundingBox({
locator: locator8,
timeout: timeBudget,
attachmentName:
"boundingrectprefixdivider492a43fa-3dac-4506-a3d4-c218f381bd17timingdividerbefore",
testInfo: testInfo,
})
);
await utilizeTimeBudget(timeBudget, async () => {
await expect(locator8).toBeVisible({ timeout: timeBudget });
});
} finally {
await page.context().tracing.groupEnd();
}
},
type: null,
retryable: false,
ignoreFailure: false,
} as const;
const step9 = {
fn: async (page: Page): Promise<void> => {
await page
.context()
.tracing.group(
"10. check that 'Sign up to deploy your project' heading contains text ''Sign up to deploy your project''",
{
location: {
file: '#octo: {"elementId":"c27ca8dd-9c56-4080-9d0a-b05ef585ba6d","ignoreFailure":false,"plannedIndex":9,"actionOrExpectation":"TO_CONTAIN_TEXT"}',
line: 1,
column: 1,
},
}
);
try {
const locator9 = page
.getByRole("heading", { name: "Sign up to deploy your project" })
.locator("visible=true");
let timeBudget = 30000;
timeBudget = await utilizeTimeBudget(
timeBudget,
async () =>
await saveScrollIntoView({
locator: locator9,
timeout: timeBudget,
})
);
timeBudget = await utilizeTimeBudget(timeBudget, async () =>
attachBoundingBox({
locator: locator9,
timeout: timeBudget,
attachmentName:
"boundingrectprefixdividerc27ca8dd-9c56-4080-9d0a-b05ef585ba6dtimingdividerbefore",
testInfo: testInfo,
})
);
await utilizeTimeBudget(timeBudget, async () => {
await expect(locator9).toContainText(
"Sign up to deploy your project",
{ timeout: timeBudget }
);
});
} finally {
await page.context().tracing.groupEnd();
}
},
type: null,
retryable: false,
ignoreFailure: false,
} as const;
const unfilteredSteps = [
step0,
step1,
step2,
step3,
step4,
step5,
step6,
step7,
step8,
step9,
];
let stepsToExecute = filterSteps(
unfilteredSteps,
new Set<"LOGIN" | "COOKIE_BANNER" | null>(["LOGIN", "COOKIE_BANNER"])
);
const onlyCookieBannerSkipped = filterSteps(
unfilteredSteps,
new Set<"LOGIN" | "COOKIE_BANNER" | null>(["COOKIE_BANNER"])
);
let originalExceptionBeforeSharedAuthStateRetry: unknown = undefined;
for (let i = 0; i < stepsToExecute.length; i++) {
try {
try {
await stepsToExecute[i]?.fn(activePage);
} catch (innerError) {
const errorMessage =
innerError instanceof Error
? innerError.message
: "unknown error";
const strictModeViolation = /.+strict mode violation.+/;
if (errorMessage.match(strictModeViolation) !== null) {
// retry due to https://github.com/microsoft/playwright/issues/32852
await activePage.waitForTimeout(5000);
await stepsToExecute[i]?.fn(activePage);
} else {
throw innerError;
}
}
} catch (e) {
if (newPage || activePage.isClosed()) {
if (newPage) {
activePage = newPage;
newPage = undefined;
} else if (activePage.isClosed()) {
const previousPage = context.pages().at(-1);
if (!previousPage) {
throw new Error("No open page left, aborting run!");
}
activePage = previousPage;
}
i -= 1;
continue;
}
if (stepsToExecute[i]?.ignoreFailure) {
continue;
}
if (originalExceptionBeforeSharedAuthStateRetry) {
throw originalExceptionBeforeSharedAuthStateRetry;
}
if (stepsToExecute !== onlyCookieBannerSkipped) {
originalExceptionBeforeSharedAuthStateRetry = e;
stepsToExecute = onlyCookieBannerSkipped;
i = 0;
continue;
}
throw e;
}
}
if (stepsToExecute.at(-1)?.type === "LOGIN") {
if (!fs.existsSync(".auth/state.json")) {
const storageState = await context.storageState();
try {
await fsPromises.mkdir(path.parse(".auth/state.json").dir, {
recursive: true,
});
} catch (error: unknown) {
if (
error &&
typeof error === "object" &&
"code" in error &&
error.code == "EEXIST"
) {
return;
}
throw error;
}
await fsPromises.writeFile(
".auth/state.json",
JSON.stringify({
...storageState,
octomind: {
urlAfterLogin: activePage.url(),
},
})
);
}
}
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment