mirror of
https://github.com/usebruno/bruno.git
synced 2026-06-11 09:51:30 +00:00
1143 lines
47 KiB
TypeScript
1143 lines
47 KiB
TypeScript
import { test, expect, Page } from '../../../playwright';
|
|
import {
|
|
closeAllCollections,
|
|
createCollection,
|
|
createRequest,
|
|
selectRequestPaneTab,
|
|
selectResponsePaneTab,
|
|
selectScriptSubTab,
|
|
openCollection,
|
|
openRequest,
|
|
sendRequest
|
|
} from '../../utils/page';
|
|
import { buildCommonLocators } from '../../utils/page/locators';
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Content generators - produce enough content to make each area scrollable
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const generateLargeJson = () => JSON.stringify(
|
|
{ users: Array.from({ length: 50 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com` })) },
|
|
null, 2
|
|
);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CodeMirror helpers - interact with CM5 instances by CSS selector
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const getEditorScroll = async (page: Page, selector: string): Promise<number> => {
|
|
const editor = page.locator(selector).first();
|
|
return editor.evaluate((el) => {
|
|
const cm = (el as any).CodeMirror;
|
|
if (!cm) return 0;
|
|
const info = cm.getScrollInfo();
|
|
return info?.top ?? 0;
|
|
});
|
|
};
|
|
|
|
const setEditorScroll = async (page: Page, selector: string, scrollTop: number) => {
|
|
const editor = page.locator(selector).first();
|
|
// Ensure content is laid out
|
|
await editor.evaluate((el) => {
|
|
const cm = (el as any).CodeMirror;
|
|
cm?.refresh();
|
|
});
|
|
// Use mouse wheel to simulate real user scrolling
|
|
await editor.hover();
|
|
const box = await editor.boundingBox();
|
|
if (box) {
|
|
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
|
const scrollStep = 200;
|
|
const steps = Math.ceil(scrollTop / scrollStep);
|
|
for (let i = 0; i < steps; i++) {
|
|
await page.mouse.wheel(0, scrollStep);
|
|
await page.waitForTimeout(50);
|
|
}
|
|
}
|
|
await page.waitForTimeout(300);
|
|
|
|
// In Playwright's Electron environment, CM5's internal 'scroll' event may not
|
|
// fire reliably from mouse.wheel. Emit it manually so the persistence hook's
|
|
// onScroll handler fires and updates scrollPosRef + debounced localStorage save.
|
|
await editor.evaluate((el) => {
|
|
const cm = (el as any).CodeMirror;
|
|
if (cm && cm.constructor?.signal) {
|
|
cm.constructor.signal(cm, 'scroll', cm);
|
|
}
|
|
});
|
|
// Wait for debounced save (200ms) to complete
|
|
await page.waitForTimeout(400);
|
|
};
|
|
|
|
const setEditorContent = async (page: Page, selector: string, content: string) => {
|
|
const editor = page.locator(selector).first();
|
|
await editor.evaluate((el, value) => {
|
|
const cm = (el as any).CodeMirror;
|
|
if (!cm) return;
|
|
cm.setValue(value);
|
|
cm.refresh();
|
|
}, content);
|
|
// Wait for CodeMirror to calculate scroll height for new content
|
|
await page.waitForTimeout(200);
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Body mode helper
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const selectBodyMode = async (page: Page, mode: string) => {
|
|
await page.locator('.body-mode-selector').click();
|
|
await page.locator('.dropdown-item').filter({ hasText: mode }).click();
|
|
await page.waitForTimeout(100);
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Common assertion: scroll position is approximately restored
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// CodeMirror layout sub-pixel rounding, virtualised list buffers, and remount
|
|
// timing can shift the restored scroll by a small amount even when persistence
|
|
// is working correctly — assert "close to" rather than exact.
|
|
const expectScrollRestored = (restored: number, original: number) => {
|
|
expect(restored).toBeGreaterThan(0);
|
|
// 10% tolerance, with a 50px floor so small captured values still pass
|
|
const tolerance = Math.max(50, original * 0.1);
|
|
expect(restored).toBeGreaterThan(original - tolerance);
|
|
expect(restored).toBeLessThan(original + tolerance);
|
|
};
|
|
|
|
// For virtualised tables: assert the first-visible row index is near the expected
|
|
// row, tolerating TableVirtuoso's buffer drift (±a few rows).
|
|
const expectRowNear = (actual: number, expected: number, tolerance: number = 5) => {
|
|
expect(actual).toBeGreaterThan(expected - tolerance);
|
|
expect(actual).toBeLessThan(expected + tolerance);
|
|
};
|
|
|
|
// Tests share an Electron worker, so localStorage carries scroll values from one
|
|
// test into the next under the same `persisted::<tabUid>::<key>` namespace —
|
|
// breaking the "initial scroll is 0" assertion. Wipe just the scroll-persistence
|
|
// keys at the start of each test that exercises the fixture collection.
|
|
const clearPersistedScrollState = async (page: Page) => {
|
|
await page.evaluate(() => {
|
|
try {
|
|
Object.keys(localStorage)
|
|
.filter((k) => k.startsWith('persisted::'))
|
|
.forEach((k) => localStorage.removeItem(k));
|
|
} catch {}
|
|
});
|
|
};
|
|
|
|
// ===========================================================================
|
|
// REQUEST PANE - scroll persistence
|
|
// ===========================================================================
|
|
|
|
test.describe('Scroll Position Persistence', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await closeAllCollections(page);
|
|
});
|
|
|
|
test.afterAll(async ({ page }) => {
|
|
await closeAllCollections(page);
|
|
});
|
|
// -------------------------------------------------------------------------
|
|
// Request Pane
|
|
// -------------------------------------------------------------------------
|
|
|
|
test.describe('Request Pane', () => {
|
|
test.beforeEach(async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
await clearPersistedScrollState(page);
|
|
});
|
|
|
|
test.afterAll(async ({ page }) => {
|
|
await closeAllCollections(page);
|
|
});
|
|
|
|
test('Body (JSON) - scroll persists across tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
|
|
await test.step('Setup', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await openRequest(page, 'scroll-fixtures', 'body-json');
|
|
await selectRequestPaneTab(page, 'Body');
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, '.request-pane .CodeMirror');
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
let saved: number;
|
|
|
|
await test.step('Switch to Headers then back to Body', async () => {
|
|
await selectRequestPaneTab(page, 'Headers');
|
|
await selectRequestPaneTab(page, 'Body');
|
|
await setEditorScroll(page, '.request-pane .CodeMirror', 1500);
|
|
});
|
|
|
|
await test.step('Scroll down and capture position', async () => {
|
|
saved = await getEditorScroll(page, '.request-pane .CodeMirror');
|
|
expectScrollRestored(saved, 1500);
|
|
});
|
|
|
|
await test.step('Switch to Headers then back to Body', async () => {
|
|
await selectRequestPaneTab(page, 'Headers');
|
|
|
|
await selectRequestPaneTab(page, 'Body');
|
|
});
|
|
|
|
await test.step('Verify scroll restored', async () => {
|
|
const checkNewPosition = await getEditorScroll(page, '.request-pane .CodeMirror');
|
|
expectScrollRestored(checkNewPosition, saved);
|
|
});
|
|
});
|
|
|
|
test('Body (XML) - scroll persists across tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
|
|
await test.step('Setup', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await openRequest(page, 'scroll-fixtures', 'body-xml');
|
|
await selectRequestPaneTab(page, 'Body');
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, '.request-pane .CodeMirror');
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
let saved: number;
|
|
|
|
await test.step('Initialize hook via tab switch, then scroll', async () => {
|
|
await selectRequestPaneTab(page, 'Params');
|
|
|
|
await selectRequestPaneTab(page, 'Body');
|
|
|
|
await setEditorScroll(page, '.request-pane .CodeMirror', 1500);
|
|
});
|
|
|
|
await test.step('Capture scroll position', async () => {
|
|
saved = await getEditorScroll(page, '.request-pane .CodeMirror');
|
|
expectScrollRestored(saved, 1500);
|
|
});
|
|
|
|
await test.step('Switch to Params then back to Body', async () => {
|
|
await selectRequestPaneTab(page, 'Params');
|
|
|
|
await selectRequestPaneTab(page, 'Body');
|
|
});
|
|
|
|
await test.step('Verify scroll restored', async () => {
|
|
const restored = await getEditorScroll(page, '.request-pane .CodeMirror');
|
|
expectScrollRestored(restored, saved);
|
|
});
|
|
});
|
|
|
|
test('Script - pre-request and post-response scroll persists across sub-tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
const PRE_SELECTOR = '[data-testid="pre-request-script-editor"] .CodeMirror';
|
|
const POST_SELECTOR = '[data-testid="post-response-script-editor"] .CodeMirror';
|
|
|
|
let preReqSaved: number;
|
|
let postResSaved: number;
|
|
|
|
// --- Pre-request: add content, init hook, scroll, verify ---
|
|
|
|
await test.step('Open script request and switch to pre-request', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await openRequest(page, 'scroll-fixtures', 'script');
|
|
await selectRequestPaneTab(page, 'Script');
|
|
await selectScriptSubTab(page, 'pre-request');
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, PRE_SELECTOR);
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
await test.step('Init pre-request hook: switch to Headers and back', async () => {
|
|
await selectRequestPaneTab(page, 'Headers');
|
|
|
|
await selectRequestPaneTab(page, 'Script');
|
|
});
|
|
|
|
await test.step('Scroll pre-request editor', async () => {
|
|
await selectScriptSubTab(page, 'pre-request');
|
|
|
|
await setEditorScroll(page, PRE_SELECTOR, 1500);
|
|
preReqSaved = await getEditorScroll(page, PRE_SELECTOR);
|
|
expectScrollRestored(preReqSaved, 1500);
|
|
});
|
|
|
|
await test.step('Verify pre-request: switch to Headers and back', async () => {
|
|
await selectRequestPaneTab(page, 'Headers');
|
|
|
|
await selectScriptSubTab(page, 'post-response');
|
|
|
|
await selectScriptSubTab(page, 'pre-request');
|
|
|
|
const restored = await getEditorScroll(page, PRE_SELECTOR);
|
|
expectScrollRestored(restored, preReqSaved);
|
|
});
|
|
|
|
// --- Post-response: add content, init hook, scroll, verify ---
|
|
|
|
await test.step('Switch to post-response', async () => {
|
|
await selectScriptSubTab(page, 'post-response');
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, POST_SELECTOR);
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
await test.step('Init post-response hook: switch to Headers and back', async () => {
|
|
await selectRequestPaneTab(page, 'Headers');
|
|
|
|
await selectRequestPaneTab(page, 'Script');
|
|
|
|
await selectScriptSubTab(page, 'post-response');
|
|
});
|
|
|
|
await test.step('Scroll post-response editor', async () => {
|
|
await setEditorScroll(page, POST_SELECTOR, 1500);
|
|
postResSaved = await getEditorScroll(page, POST_SELECTOR);
|
|
expectScrollRestored(postResSaved, 1500);
|
|
});
|
|
|
|
await test.step('Verify post-response: switch to Headers and back', async () => {
|
|
await selectRequestPaneTab(page, 'Headers');
|
|
|
|
await selectRequestPaneTab(page, 'Script');
|
|
|
|
await selectScriptSubTab(page, 'post-response');
|
|
|
|
const restored = await getEditorScroll(page, POST_SELECTOR);
|
|
expectScrollRestored(restored, postResSaved);
|
|
});
|
|
|
|
// --- Final check: both persist across pre/post sub-tab switch ---
|
|
|
|
await test.step('Verify pre-request still persisted after post-response check', async () => {
|
|
await selectScriptSubTab(page, 'pre-request');
|
|
|
|
const restored = await getEditorScroll(page, PRE_SELECTOR);
|
|
expectScrollRestored(restored, preReqSaved);
|
|
});
|
|
|
|
await test.step('Verify post-response still persisted after pre-request check', async () => {
|
|
await selectScriptSubTab(page, 'post-response');
|
|
|
|
const restored = await getEditorScroll(page, POST_SELECTOR);
|
|
expectScrollRestored(restored, postResSaved);
|
|
});
|
|
});
|
|
|
|
test('Tests editor - scroll persists across tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
|
|
await test.step('Setup', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await openRequest(page, 'scroll-fixtures', 'tests');
|
|
await selectRequestPaneTab(page, 'Tests');
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, '[data-testid="test-script-editor"] .CodeMirror');
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
let saved: number;
|
|
|
|
await test.step('Initialize hook via tab switch, then scroll', async () => {
|
|
await selectRequestPaneTab(page, 'Vars');
|
|
|
|
await selectRequestPaneTab(page, 'Tests');
|
|
|
|
await setEditorScroll(page, '[data-testid="test-script-editor"] .CodeMirror', 1500);
|
|
});
|
|
|
|
await test.step('Capture scroll position', async () => {
|
|
saved = await getEditorScroll(page, '[data-testid="test-script-editor"] .CodeMirror');
|
|
expectScrollRestored(saved, 1500);
|
|
});
|
|
|
|
await test.step('Switch to Body then back to Tests', async () => {
|
|
await selectRequestPaneTab(page, 'Vars');
|
|
await selectRequestPaneTab(page, 'Tests');
|
|
});
|
|
|
|
await test.step('Verify scroll restored', async () => {
|
|
const restored = await getEditorScroll(page, '[data-testid="test-script-editor"] .CodeMirror');
|
|
expectScrollRestored(restored, saved);
|
|
});
|
|
});
|
|
|
|
test('Scroll positions are independent per request', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
|
|
await test.step('Open collection', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
});
|
|
|
|
let scrollA: number;
|
|
|
|
await test.step('Open req-a and navigate to Body', async () => {
|
|
await openRequest(page, 'scroll-fixtures', 'req-a');
|
|
await selectRequestPaneTab(page, 'Body');
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, '.request-pane .CodeMirror');
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
await test.step('Initialize hook via tab switch, then scroll', async () => {
|
|
// Initialize hook
|
|
await selectRequestPaneTab(page, 'Headers');
|
|
|
|
await selectRequestPaneTab(page, 'Body');
|
|
|
|
await setEditorScroll(page, '.request-pane .CodeMirror', 1500);
|
|
});
|
|
|
|
await test.step('Capture scroll position', async () => {
|
|
scrollA = await getEditorScroll(page, '.request-pane .CodeMirror');
|
|
expectScrollRestored(scrollA, 1500);
|
|
});
|
|
|
|
await test.step('Switch to req-b', async () => {
|
|
await openRequest(page, 'scroll-fixtures', 'req-b');
|
|
});
|
|
|
|
await test.step('Switch back to req-a and verify scroll', async () => {
|
|
await openRequest(page, 'scroll-fixtures', 'req-a');
|
|
await selectRequestPaneTab(page, 'Body');
|
|
|
|
const restored = await getEditorScroll(page, '.request-pane .CodeMirror');
|
|
expectScrollRestored(restored, scrollA);
|
|
});
|
|
});
|
|
|
|
test('Request Headers - scroll persists with many headers across tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
const scrollContainer = '.flex-boundary';
|
|
const firstVisibleRowLocator = () => page.getByTestId('editable-table').locator('table > tbody > tr:nth-child(2)');
|
|
|
|
await test.step('Setup request and navigate to Headers tab', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await openRequest(page, 'scroll-fixtures', 'headers-many');
|
|
await selectRequestPaneTab(page, 'Headers');
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const container = page.locator(scrollContainer).first();
|
|
const initial = await container.evaluate((el) => el.scrollTop);
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
await test.step('Scroll to ~middle of table (~row 50)', async () => {
|
|
const container = page.locator(scrollContainer).first();
|
|
// Scroll halfway through the virtualised list so ~row 50 becomes the first visible row
|
|
await container.evaluate((el) => { el.scrollTop = el.scrollHeight / 2; });
|
|
|
|
// Auto-retry: wait for TableVirtuoso to land on a row in [45, 55]
|
|
// (matches the ~row 50 ± 5 range that expectRowNear asserts)
|
|
const element = firstVisibleRowLocator();
|
|
await expect(element).toHaveAttribute('data-index', /^(4[5-9]|5[0-5])$/, { timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Switch to Body tab and back to Headers', async () => {
|
|
await selectRequestPaneTab(page, 'Body');
|
|
await selectRequestPaneTab(page, 'Headers');
|
|
const tableRow = page.getByRole('row', { name: 'Name Value' }).getByRole('cell').first();
|
|
await expect(tableRow).toBeVisible({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify scroll restored to ~row 50', async () => {
|
|
const element = firstVisibleRowLocator();
|
|
const current = parseInt(await element.getAttribute('data-index') as string);
|
|
expectRowNear(current, 50);
|
|
});
|
|
});
|
|
|
|
test('Assertions - scroll persists with many assertions across tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
const scrollContainer = '.flex-boundary';
|
|
// Match the first row that actually has a data-index attribute. This skips
|
|
// Virtuoso's optional top-spacer tr (which has no data-index and is only
|
|
// rendered when scrolled past row 0).
|
|
const firstVisibleRowLocator = () =>
|
|
page.getByTestId('assertions-table').locator('table > tbody > tr[data-index]').first();
|
|
|
|
await test.step('Setup request and navigate to Assertions tab', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await openRequest(page, 'scroll-fixtures', 'assertions-many');
|
|
await selectRequestPaneTab(page, 'Assert');
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const container = page.locator(scrollContainer).first();
|
|
await container.evaluate((el) => { el.scrollTop = 0; });
|
|
await expect(firstVisibleRowLocator()).toHaveAttribute('data-index', '0', { timeout: 2000 });
|
|
const initial = await container.evaluate((el) => el.scrollTop);
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
await test.step('Scroll to ~middle of table (~row 30)', async () => {
|
|
const container = page.locator(scrollContainer).first();
|
|
await container.evaluate((el) => { el.scrollTop = el.scrollHeight / 2; });
|
|
|
|
const element = firstVisibleRowLocator();
|
|
await expect(element).toHaveAttribute('data-index', /^(2[5-9]|3[0-5])$/, { timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Switch to Body tab and back to Assert', async () => {
|
|
await selectRequestPaneTab(page, 'Body');
|
|
await selectRequestPaneTab(page, 'Assert');
|
|
const header = page.getByTestId('assertions-table').locator('table thead tr').first();
|
|
await expect(header).toBeVisible({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify scroll restored to ~row 30', async () => {
|
|
const element = firstVisibleRowLocator();
|
|
await expect(element).toHaveAttribute('data-index', /^(2[5-9]|3[0-5])$/, { timeout: 2000 });
|
|
const current = parseInt(await element.getAttribute('data-index') as string);
|
|
expectRowNear(current, 30);
|
|
});
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Response Pane
|
|
// -------------------------------------------------------------------------
|
|
|
|
test.describe('Response Pane', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await closeAllCollections(page);
|
|
});
|
|
|
|
test.afterAll(async ({ page }) => {
|
|
await closeAllCollections(page);
|
|
});
|
|
|
|
test('Response body - scroll persists across response tab switches', async ({ page, createTmpDir }) => {
|
|
const tmpDir = await createTmpDir('scroll-response');
|
|
const responseEditor = '.response-pane .CodeMirror';
|
|
|
|
await test.step('Create collection, request, set JSON body and send', async () => {
|
|
await createCollection(page, 'scroll-response', tmpDir);
|
|
await createRequest(page, 'req-resp', 'scroll-response', { url: 'https://jsonplaceholder.typicode.com/todos' });
|
|
await selectRequestPaneTab(page, 'Body');
|
|
await selectBodyMode(page, 'JSON');
|
|
await setEditorContent(page, '.request-pane .CodeMirror', generateLargeJson());
|
|
await sendRequest(page, 200);
|
|
});
|
|
|
|
let saved: number;
|
|
|
|
await test.step('Initialize hook: switch response tabs', async () => {
|
|
await selectResponsePaneTab(page, 'Response');
|
|
await selectResponsePaneTab(page, 'Headers');
|
|
await selectResponsePaneTab(page, 'Response');
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, responseEditor);
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
await test.step('Scroll response editor and capture position', async () => {
|
|
await setEditorScroll(page, responseEditor, 1500);
|
|
saved = await getEditorScroll(page, responseEditor);
|
|
expectScrollRestored(saved, 1500);
|
|
});
|
|
|
|
await test.step('Switch to Headers tab and back', async () => {
|
|
await selectResponsePaneTab(page, 'Headers');
|
|
|
|
await selectResponsePaneTab(page, 'Response');
|
|
});
|
|
|
|
await test.step('Verify scroll restored', async () => {
|
|
const restored = await getEditorScroll(page, responseEditor);
|
|
expectScrollRestored(restored, saved);
|
|
});
|
|
});
|
|
|
|
test('Response headers - scroll persists across response tab switches', async ({ page, createTmpDir }) => {
|
|
const tmpDir = await createTmpDir('scroll-response-headers');
|
|
const headersContainer = '.response-tab-content';
|
|
|
|
await test.step('Create collection, request and send to get response headers', async () => {
|
|
await createCollection(page, 'scroll-response-headers', tmpDir);
|
|
await createRequest(page, 'req-resp-headers', 'scroll-response-headers', { url: 'https://jsonplaceholder.typicode.com/todos' });
|
|
await sendRequest(page, 200);
|
|
});
|
|
|
|
let saved: number;
|
|
|
|
await test.step('Initialize hook: switch response tabs', async () => {
|
|
await selectResponsePaneTab(page, 'Headers');
|
|
|
|
await selectResponsePaneTab(page, 'Response');
|
|
|
|
await selectResponsePaneTab(page, 'Headers');
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const container = page.locator(headersContainer).first();
|
|
const initial = await container.evaluate((el) => el.scrollTop);
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
await test.step('Scroll response headers and capture position', async () => {
|
|
const container = page.locator(headersContainer).first();
|
|
await container.evaluate((el) => { el.scrollTop = 200; });
|
|
|
|
saved = await container.evaluate((el) => el.scrollTop);
|
|
expectScrollRestored(saved, 200);
|
|
});
|
|
|
|
await test.step('Switch to Response tab and back to Headers', async () => {
|
|
await selectResponsePaneTab(page, 'Response');
|
|
|
|
await selectResponsePaneTab(page, 'Headers');
|
|
});
|
|
|
|
await test.step('Verify scroll restored', async () => {
|
|
const container = page.locator(headersContainer).first();
|
|
const restored = await container.evaluate((el) => el.scrollTop);
|
|
expectScrollRestored(restored, saved);
|
|
});
|
|
});
|
|
|
|
test('Response timeline - scroll persists across response tab switches', async ({ page, createTmpDir }) => {
|
|
const tmpDir = await createTmpDir('scroll-response-timeline');
|
|
const timelineScroller = '.timeline-container';
|
|
|
|
await test.step('Create collection and request', async () => {
|
|
await createCollection(page, 'scroll-response-timeline', tmpDir);
|
|
await createRequest(page, 'req-timeline', 'scroll-response-timeline', { url: 'http://localhost:8081/ping' });
|
|
});
|
|
|
|
await test.step('Send and cancel requests to generate timeline entries', async () => {
|
|
const sendBtn = page.getByTestId('send-arrow-icon');
|
|
for (let i = 0; i < 25; i++) {
|
|
await sendBtn.click({ timeout: 2000 });
|
|
// Immediately cancel - we just need the timeline entry, not the response
|
|
await sendBtn.click({ timeout: 2000 });
|
|
}
|
|
});
|
|
|
|
let saved: number;
|
|
|
|
await test.step('Switch to Timeline tab', async () => {
|
|
await selectResponsePaneTab(page, 'Timeline');
|
|
});
|
|
|
|
await test.step('Initialize hook: switch tabs', async () => {
|
|
await selectResponsePaneTab(page, 'Response');
|
|
|
|
await selectResponsePaneTab(page, 'Timeline');
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const container = page.locator(timelineScroller).first().locator('..');
|
|
const initial = await container.evaluate((el) => el.scrollTop);
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
await test.step('Scroll timeline and capture position', async () => {
|
|
// Timeline StyledWrapper is the parent of .timeline-container
|
|
const container = page.locator(timelineScroller).first();
|
|
const scrollParent = container.locator('..');
|
|
await scrollParent.evaluate((el) => { el.scrollTop = 500; });
|
|
|
|
saved = await scrollParent.evaluate((el) => el.scrollTop);
|
|
expectScrollRestored(saved, 500);
|
|
});
|
|
|
|
await test.step('Switch to Response tab and back to Timeline', async () => {
|
|
await selectResponsePaneTab(page, 'Response');
|
|
|
|
await selectResponsePaneTab(page, 'Timeline');
|
|
});
|
|
|
|
await test.step('Verify scroll restored', async () => {
|
|
const container = page.locator(timelineScroller).first();
|
|
const scrollParent = container.locator('..');
|
|
const restored = await scrollParent.evaluate((el) => el.scrollTop);
|
|
expectScrollRestored(restored, saved);
|
|
});
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Folder Settings
|
|
// -------------------------------------------------------------------------
|
|
|
|
test.describe('Folder Settings', () => {
|
|
test.beforeEach(async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
await clearPersistedScrollState(page);
|
|
});
|
|
|
|
test.afterAll(async ({ page }) => {
|
|
await closeAllCollections(page);
|
|
});
|
|
|
|
test('Folder Script - scroll persists across sub-tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
const locators = buildCommonLocators(page);
|
|
|
|
await test.step('Setup folder', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await locators.sidebar.folder('test-folder').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Navigate to Script tab pre-request', async () => {
|
|
await locators.paneTabs.folderSettingsTab('script').click({ timeout: 2000 });
|
|
await page.getByTestId('tab-trigger-pre-request').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, '.CodeMirror');
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
let saved: number;
|
|
|
|
await test.step('Initialize hook via tab switch, then scroll', async () => {
|
|
await locators.paneTabs.folderSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.folderSettingsTab('script').click({ timeout: 2000 });
|
|
await page.getByTestId('tab-trigger-pre-request').click({ timeout: 2000 });
|
|
await setEditorScroll(page, '.CodeMirror', 400);
|
|
});
|
|
|
|
await test.step('Capture scroll position', async () => {
|
|
saved = await getEditorScroll(page, '.CodeMirror');
|
|
expectScrollRestored(saved, 400);
|
|
});
|
|
|
|
await test.step('Switch to post-response and back', async () => {
|
|
await page.getByTestId('tab-trigger-post-response').click({ timeout: 2000 });
|
|
await page.getByTestId('tab-trigger-pre-request').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify scroll restored', async () => {
|
|
const restored = await getEditorScroll(page, '.CodeMirror');
|
|
expectScrollRestored(restored, saved);
|
|
});
|
|
});
|
|
|
|
test('Folder Tests - scroll persists across tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
const locators = buildCommonLocators(page);
|
|
|
|
await test.step('Open folder and navigate to Tests tab', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await locators.sidebar.folder('test-folder').click({ timeout: 2000 });
|
|
await locators.paneTabs.folderSettingsTab('test').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, '.CodeMirror');
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
let saved: number;
|
|
|
|
await test.step('Initialize hook via tab switch, then scroll', async () => {
|
|
await locators.paneTabs.folderSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.folderSettingsTab('test').click({ timeout: 2000 });
|
|
await setEditorScroll(page, '.CodeMirror', 1500);
|
|
});
|
|
|
|
await test.step('Capture scroll position', async () => {
|
|
saved = await getEditorScroll(page, '.CodeMirror');
|
|
expectScrollRestored(saved, 1500);
|
|
});
|
|
|
|
await test.step('Switch to headers and back', async () => {
|
|
await locators.paneTabs.folderSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.folderSettingsTab('test').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify scroll restored', async () => {
|
|
const restored = await getEditorScroll(page, '.CodeMirror');
|
|
expectScrollRestored(restored, saved);
|
|
});
|
|
});
|
|
|
|
test('Folder Docs - scroll persists in edit mode across tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
const locators = buildCommonLocators(page);
|
|
|
|
await test.step('Open folder and navigate to Docs tab', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await locators.sidebar.folder('test-folder').click({ timeout: 2000 });
|
|
await locators.paneTabs.folderSettingsTab('docs').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Click Edit', async () => {
|
|
const editToggle = page.locator('.editing-mode');
|
|
await editToggle.click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, '.CodeMirror');
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
let saved: number;
|
|
|
|
await test.step('Initialize hook via tab switch, then scroll', async () => {
|
|
await locators.paneTabs.folderSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.folderSettingsTab('docs').click({ timeout: 2000 });
|
|
await setEditorScroll(page, '.CodeMirror', 1500);
|
|
});
|
|
|
|
await test.step('Capture scroll position', async () => {
|
|
saved = await getEditorScroll(page, '.CodeMirror');
|
|
expectScrollRestored(saved, 1500);
|
|
});
|
|
|
|
await test.step('Switch to headers and back to docs edit mode', async () => {
|
|
await locators.paneTabs.folderSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.folderSettingsTab('docs').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify scroll restored', async () => {
|
|
const restored = await getEditorScroll(page, '.CodeMirror');
|
|
expectScrollRestored(restored, saved);
|
|
});
|
|
});
|
|
|
|
test('Folder Script pre-request - scroll persists across tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
const locators = buildCommonLocators(page);
|
|
const PRE_SELECTOR = '[data-testid="folder-pre-request-script-editor"] .CodeMirror';
|
|
|
|
let saved: number;
|
|
|
|
await test.step('Open folder and navigate to pre-request', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await locators.sidebar.folder('test-folder').click({ timeout: 2000 });
|
|
await locators.paneTabs.folderSettingsTab('script').click({ timeout: 2000 });
|
|
await page.getByTestId('tab-trigger-pre-request').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, PRE_SELECTOR);
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
await test.step('Init hook: switch tabs', async () => {
|
|
await locators.paneTabs.folderSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.folderSettingsTab('script').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Scroll pre-request editor', async () => {
|
|
await page.getByTestId('tab-trigger-pre-request').click({ timeout: 2000 });
|
|
await setEditorScroll(page, PRE_SELECTOR, 1500);
|
|
saved = await getEditorScroll(page, PRE_SELECTOR);
|
|
expectScrollRestored(saved, 1500);
|
|
});
|
|
|
|
await test.step('Switch to headers and back', async () => {
|
|
await locators.paneTabs.folderSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.folderSettingsTab('script').click({ timeout: 2000 });
|
|
await page.getByTestId('tab-trigger-pre-request').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify scroll restored', async () => {
|
|
const restored = await getEditorScroll(page, PRE_SELECTOR);
|
|
expectScrollRestored(restored, saved);
|
|
});
|
|
});
|
|
|
|
test('Folder Script post-response - scroll persists across tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
const locators = buildCommonLocators(page);
|
|
const POST_SELECTOR = '[data-testid="folder-post-response-script-editor"] .CodeMirror';
|
|
|
|
let saved: number;
|
|
|
|
await test.step('Open folder and navigate to post-response', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await locators.sidebar.folder('test-folder').click({ timeout: 2000 });
|
|
await locators.paneTabs.folderSettingsTab('script').click({ timeout: 2000 });
|
|
await page.getByTestId('tab-trigger-post-response').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, POST_SELECTOR);
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
await test.step('Init hook: switch tabs', async () => {
|
|
await locators.paneTabs.folderSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.folderSettingsTab('script').click({ timeout: 2000 });
|
|
await page.getByTestId('tab-trigger-post-response').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Scroll post-response editor', async () => {
|
|
await setEditorScroll(page, POST_SELECTOR, 1500);
|
|
saved = await getEditorScroll(page, POST_SELECTOR);
|
|
expectScrollRestored(saved, 1500);
|
|
});
|
|
|
|
await test.step('Switch to headers and back', async () => {
|
|
await locators.paneTabs.folderSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.folderSettingsTab('script').click({ timeout: 2000 });
|
|
await page.getByTestId('tab-trigger-post-response').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify scroll restored', async () => {
|
|
const restored = await getEditorScroll(page, POST_SELECTOR);
|
|
expectScrollRestored(restored, saved);
|
|
});
|
|
});
|
|
});
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Collection Settings
|
|
// -------------------------------------------------------------------------
|
|
|
|
test.describe('Collection Settings', () => {
|
|
test.beforeEach(async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
await clearPersistedScrollState(page);
|
|
});
|
|
|
|
test.afterAll(async ({ page }) => {
|
|
await closeAllCollections(page);
|
|
});
|
|
|
|
// Helper to open collection settings
|
|
const openCollectionSettings = async (page: Page, collName: string) => {
|
|
const locators = buildCommonLocators(page);
|
|
await locators.sidebar.collection(collName).hover();
|
|
await locators.actions.collectionActions(collName).click({ timeout: 2000 });
|
|
await locators.dropdown.item('Settings').click({ timeout: 2000 });
|
|
};
|
|
|
|
test('Collection Script - pre-request and post-response scroll persists', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
const locators = buildCommonLocators(page);
|
|
const PRE_SELECTOR = '[data-testid="collection-pre-request-script-editor"] .CodeMirror';
|
|
const POST_SELECTOR = '[data-testid="collection-post-response-script-editor"] .CodeMirror';
|
|
|
|
let preReqSaved: number;
|
|
let postResSaved: number;
|
|
|
|
// --- Pre-request ---
|
|
|
|
await test.step('Open collection settings and navigate to pre-request', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await openCollectionSettings(page, 'scroll-fixtures');
|
|
await locators.paneTabs.collectionSettingsTab('script').click({ timeout: 2000 });
|
|
await page.getByTestId('tab-trigger-pre-request').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, PRE_SELECTOR);
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
await test.step('Init pre-request hook: switch tabs', async () => {
|
|
await locators.paneTabs.collectionSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.collectionSettingsTab('script').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Scroll pre-request editor', async () => {
|
|
await page.getByTestId('tab-trigger-pre-request').click({ timeout: 2000 });
|
|
await setEditorScroll(page, PRE_SELECTOR, 1500);
|
|
preReqSaved = await getEditorScroll(page, PRE_SELECTOR);
|
|
expectScrollRestored(preReqSaved, 1500);
|
|
});
|
|
|
|
await test.step('Verify pre-request: switch to headers and back', async () => {
|
|
await locators.paneTabs.collectionSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.collectionSettingsTab('script').click({ timeout: 2000 });
|
|
await page.getByTestId('tab-trigger-pre-request').click({ timeout: 2000 });
|
|
const restored = await getEditorScroll(page, PRE_SELECTOR);
|
|
expectScrollRestored(restored, preReqSaved);
|
|
});
|
|
|
|
// --- Post-response ---
|
|
|
|
await test.step('Switch to post-response', async () => {
|
|
await page.getByTestId('tab-trigger-post-response').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, POST_SELECTOR);
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
await test.step('Init post-response hook: switch tabs', async () => {
|
|
await locators.paneTabs.collectionSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.collectionSettingsTab('script').click({ timeout: 2000 });
|
|
await page.getByTestId('tab-trigger-post-response').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Scroll post-response editor', async () => {
|
|
await setEditorScroll(page, POST_SELECTOR, 1500);
|
|
postResSaved = await getEditorScroll(page, POST_SELECTOR);
|
|
expectScrollRestored(postResSaved, 1500);
|
|
});
|
|
|
|
await test.step('Verify post-response: switch to headers and back', async () => {
|
|
await locators.paneTabs.collectionSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.collectionSettingsTab('script').click({ timeout: 2000 });
|
|
await page.getByTestId('tab-trigger-post-response').click({ timeout: 2000 });
|
|
const restored = await getEditorScroll(page, POST_SELECTOR);
|
|
expectScrollRestored(restored, postResSaved);
|
|
});
|
|
|
|
// --- Final cross-check ---
|
|
|
|
await test.step('Verify pre-request still persisted', async () => {
|
|
await page.getByTestId('tab-trigger-pre-request').click({ timeout: 2000 });
|
|
const restored = await getEditorScroll(page, PRE_SELECTOR);
|
|
expectScrollRestored(restored, preReqSaved);
|
|
});
|
|
|
|
await test.step('Verify post-response still persisted', async () => {
|
|
await page.getByTestId('tab-trigger-post-response').click({ timeout: 2000 });
|
|
const restored = await getEditorScroll(page, POST_SELECTOR);
|
|
expectScrollRestored(restored, postResSaved);
|
|
});
|
|
});
|
|
|
|
test('Collection Tests - scroll persists across tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
const locators = buildCommonLocators(page);
|
|
|
|
await test.step('Open collection settings and navigate to Tests tab', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await openCollectionSettings(page, 'scroll-fixtures');
|
|
await locators.paneTabs.collectionSettingsTab('tests').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, '.CodeMirror');
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
let saved: number;
|
|
|
|
await test.step('Init hook via tab switch, then scroll', async () => {
|
|
await locators.paneTabs.collectionSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.collectionSettingsTab('tests').click({ timeout: 2000 });
|
|
await setEditorScroll(page, '.CodeMirror', 1500);
|
|
});
|
|
|
|
await test.step('Capture scroll position', async () => {
|
|
saved = await getEditorScroll(page, '.CodeMirror');
|
|
expectScrollRestored(saved, 1500);
|
|
});
|
|
|
|
await test.step('Switch to headers and back', async () => {
|
|
await locators.paneTabs.collectionSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.collectionSettingsTab('tests').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify scroll restored', async () => {
|
|
const restored = await getEditorScroll(page, '.CodeMirror');
|
|
expectScrollRestored(restored, saved);
|
|
});
|
|
});
|
|
|
|
test('Collection Docs - scroll persists in edit mode across tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
const locators = buildCommonLocators(page);
|
|
|
|
await test.step('Open collection settings and navigate to Docs tab', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await openCollectionSettings(page, 'scroll-fixtures');
|
|
await locators.paneTabs.collectionSettingsTab('overview').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Click Edit', async () => {
|
|
// Collection docs has an edit icon button
|
|
const editBtn = page.locator('.editing-mode');
|
|
await editBtn.click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const initial = await getEditorScroll(page, '.CodeMirror');
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
let saved: number;
|
|
|
|
await test.step('Init hook via tab switch, then scroll', async () => {
|
|
await locators.paneTabs.collectionSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.collectionSettingsTab('overview').click({ timeout: 2000 });
|
|
await setEditorScroll(page, '.CodeMirror', 1500);
|
|
});
|
|
|
|
await test.step('Capture scroll position', async () => {
|
|
saved = await getEditorScroll(page, '.CodeMirror');
|
|
expectScrollRestored(saved, 1500);
|
|
});
|
|
|
|
await test.step('Switch to headers and back to docs edit mode', async () => {
|
|
await locators.paneTabs.collectionSettingsTab('headers').click({ timeout: 2000 });
|
|
await locators.paneTabs.collectionSettingsTab('overview').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify scroll restored', async () => {
|
|
const restored = await getEditorScroll(page, '.CodeMirror');
|
|
expectScrollRestored(restored, saved);
|
|
});
|
|
});
|
|
|
|
test('Collection Headers - scroll persists with many headers across tab switches', async ({ pageWithUserData: page }) => {
|
|
await page.locator('[data-app-state="loaded"]').waitFor({ timeout: 30000 });
|
|
const locators = buildCommonLocators(page);
|
|
const scrollContainer = '.collection-settings-content';
|
|
const firstVisibleRowLocator = () => page.getByTestId('editable-table').locator('table > tbody > tr:nth-child(2)');
|
|
|
|
await test.step('Open collection settings and navigate to Headers tab', async () => {
|
|
await openCollection(page, 'scroll-fixtures');
|
|
await openCollectionSettings(page, 'scroll-fixtures');
|
|
await locators.paneTabs.collectionSettingsTab('headers').click({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify initial scroll is 0', async () => {
|
|
const container = page.locator(scrollContainer).first();
|
|
const initial = await container.evaluate((el) => el.scrollTop);
|
|
expect(initial).toBe(0);
|
|
});
|
|
|
|
await test.step('Scroll to ~middle of table (~row 50)', async () => {
|
|
const container = page.locator(scrollContainer).first();
|
|
// Scroll halfway through the virtualised list so ~row 50 becomes the first visible row
|
|
await container.evaluate((el) => { el.scrollTop = el.scrollHeight / 2; });
|
|
|
|
// Auto-retry: wait for TableVirtuoso to land on a row in [45, 55]
|
|
// (matches the ~row 50 ± 5 range that expectRowNear asserts)
|
|
const element = firstVisibleRowLocator();
|
|
await expect(element).toHaveAttribute('data-index', /^(4[5-9]|5[0-5])$/, { timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Switch to script tab and back to headers', async () => {
|
|
await locators.paneTabs.collectionSettingsTab('script').click({ timeout: 2000 });
|
|
await locators.paneTabs.collectionSettingsTab('headers').click({ timeout: 2000 });
|
|
const tableRow = page.getByRole('row', { name: 'Name Value' }).getByRole('cell').first();
|
|
await expect(tableRow).toBeVisible({ timeout: 2000 });
|
|
});
|
|
|
|
await test.step('Verify scroll restored to ~row 50', async () => {
|
|
const element = firstVisibleRowLocator();
|
|
const current = parseInt(await element.getAttribute('data-index') as string);
|
|
expectRowNear(current, 50);
|
|
});
|
|
});
|
|
});
|
|
});
|