resolve e2e
This commit is contained in:
@@ -18,6 +18,12 @@ import {
|
||||
* - Auto-save functionality
|
||||
*/
|
||||
|
||||
const revealEditorHeader = async (page: import("@playwright/test").Page) => {
|
||||
// Editor header auto-hides after a short delay unless pointer is near the top edge.
|
||||
await page.mouse.move(24, 2);
|
||||
await page.waitForTimeout(150);
|
||||
};
|
||||
|
||||
test.describe("Drawing Creation", () => {
|
||||
let createdDrawingIds: string[] = [];
|
||||
|
||||
@@ -104,8 +110,11 @@ test.describe("Drawing Creation", () => {
|
||||
await page.goto(`/editor/${drawing.id}`);
|
||||
await page.waitForSelector("[class*='excalidraw'], canvas", { timeout: 15000 });
|
||||
|
||||
await revealEditorHeader(page);
|
||||
|
||||
// Click on the drawing name to edit it - it's a button that becomes an input
|
||||
const nameElement = page.getByText(originalName);
|
||||
await expect(nameElement).toBeInViewport();
|
||||
await nameElement.dblclick();
|
||||
|
||||
// Wait for edit mode
|
||||
@@ -132,9 +141,12 @@ test.describe("Drawing Creation", () => {
|
||||
await page.goto(`/editor/${drawing.id}`);
|
||||
await page.waitForSelector("[class*='excalidraw'], canvas", { timeout: 15000 });
|
||||
|
||||
await revealEditorHeader(page);
|
||||
|
||||
// Find and click the back button (arrow left icon in header)
|
||||
// The back button is a button element containing an ArrowLeft icon
|
||||
const backButton = page.locator("header button").first();
|
||||
await expect(backButton).toBeInViewport();
|
||||
await backButton.click();
|
||||
|
||||
// Should navigate back to dashboard
|
||||
|
||||
+32
-21
@@ -13,11 +13,20 @@ type CsrfTokenResponse = {
|
||||
type CsrfInfo = {
|
||||
token: string;
|
||||
headerName: string;
|
||||
cookieHeader: string;
|
||||
};
|
||||
|
||||
// Cache CSRF tokens per Playwright request context so parallel tests don't race.
|
||||
const csrfInfoByRequest = new WeakMap<APIRequestContext, CsrfInfo>();
|
||||
const csrfFetchByRequest = new WeakMap<APIRequestContext, Promise<CsrfInfo>>();
|
||||
let sharedCsrfInfo: CsrfInfo | null = null;
|
||||
let sharedCsrfFetch: Promise<CsrfInfo> | null = null;
|
||||
|
||||
const extractCookieHeader = (response: { headersArray: () => Array<{ name: string; value: string }> }): string => {
|
||||
const cookiePairs = response
|
||||
.headersArray()
|
||||
.filter((h) => h.name.toLowerCase() === "set-cookie")
|
||||
.map((h) => h.value.split(";")[0] || "")
|
||||
.filter((v) => v.length > 0);
|
||||
return cookiePairs.join("; ");
|
||||
};
|
||||
|
||||
const fetchCsrfInfo = async (request: APIRequestContext): Promise<CsrfInfo> => {
|
||||
const response = await request.get(`${API_URL}/csrf-token`);
|
||||
@@ -38,48 +47,50 @@ const fetchCsrfInfo = async (request: APIRequestContext): Promise<CsrfInfo> => {
|
||||
? data.header
|
||||
: "x-csrf-token";
|
||||
|
||||
return { token: data.token, headerName };
|
||||
const cookieHeader = extractCookieHeader(response);
|
||||
if (!cookieHeader) {
|
||||
throw new Error("Failed to fetch CSRF token: missing csrf client cookie");
|
||||
}
|
||||
|
||||
return { token: data.token, headerName, cookieHeader };
|
||||
};
|
||||
|
||||
const getCsrfInfo = async (request: APIRequestContext): Promise<CsrfInfo> => {
|
||||
const cached = csrfInfoByRequest.get(request);
|
||||
if (cached) return cached;
|
||||
if (sharedCsrfInfo) return sharedCsrfInfo;
|
||||
if (sharedCsrfFetch) return sharedCsrfFetch;
|
||||
|
||||
const inFlight = csrfFetchByRequest.get(request);
|
||||
if (inFlight) return inFlight;
|
||||
|
||||
const promise = fetchCsrfInfo(request)
|
||||
sharedCsrfFetch = fetchCsrfInfo(request)
|
||||
.then((info) => {
|
||||
csrfInfoByRequest.set(request, info);
|
||||
sharedCsrfInfo = info;
|
||||
return info;
|
||||
})
|
||||
.finally(() => {
|
||||
csrfFetchByRequest.delete(request);
|
||||
sharedCsrfFetch = null;
|
||||
});
|
||||
|
||||
csrfFetchByRequest.set(request, promise);
|
||||
return promise;
|
||||
return sharedCsrfFetch;
|
||||
};
|
||||
|
||||
const refreshCsrfInfo = async (request: APIRequestContext): Promise<CsrfInfo> => {
|
||||
const promise = fetchCsrfInfo(request)
|
||||
sharedCsrfFetch = fetchCsrfInfo(request)
|
||||
.then((info) => {
|
||||
csrfInfoByRequest.set(request, info);
|
||||
sharedCsrfInfo = info;
|
||||
return info;
|
||||
})
|
||||
.finally(() => {
|
||||
csrfFetchByRequest.delete(request);
|
||||
sharedCsrfFetch = null;
|
||||
});
|
||||
|
||||
csrfFetchByRequest.set(request, promise);
|
||||
return promise;
|
||||
return sharedCsrfFetch;
|
||||
};
|
||||
|
||||
export async function getCsrfHeaders(
|
||||
request: APIRequestContext
|
||||
): Promise<Record<string, string>> {
|
||||
const info = await getCsrfInfo(request);
|
||||
return { [info.headerName]: info.token };
|
||||
return {
|
||||
[info.headerName]: info.token,
|
||||
Cookie: info.cookieHeader,
|
||||
};
|
||||
}
|
||||
|
||||
const withCsrfHeaders = async (
|
||||
|
||||
Reference in New Issue
Block a user