Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions page-objects/api-contract-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ export class ApiContractPage extends BasePage {

private readonly sidebarProcessBar: (specName: string) => Locator;

readonly infoDialog: Locator;

constructor(page: Page, testInfo: TestInfo, eyes: any, specName: string) {
super(page, testInfo, eyes, specName);
this.specTree = page.locator("#spec-tree");
Expand Down Expand Up @@ -218,6 +220,8 @@ export class ApiContractPage extends BasePage {
this.includeButton = this.specSection
.locator("button.clear")
.filter({ hasText: "Include" });

this.infoDialog = this.page.locator("#alert-container .alert-msg.info");
}

//Function Beginning
Expand Down Expand Up @@ -762,7 +766,9 @@ export class ApiContractPage extends BasePage {
const summary = this.getPrereqErrorSummary();
const message = this.getPrereqErrorMessage();

const isVisible = await summary.isVisible({ timeout: 3000 }).catch(() => false);
const isVisible = await summary
.isVisible({ timeout: 3000 })
.catch(() => false);
if (!isVisible) {
console.log("No prerequisite error detected.");
return;
Expand All @@ -775,9 +781,13 @@ export class ApiContractPage extends BasePage {

await summary.click();

await expect(message).toBeVisible({ timeout: 5000 }).catch(() => {
console.warn("Detail message did not become visible after clicking summary");
});
await expect(message)
.toBeVisible({ timeout: 5000 })
.catch(() => {
console.warn(
"Detail message did not become visible after clicking summary",
);
});

await takeAndAttachScreenshot(this.page, "contract-prereq-error-expanded");

Expand All @@ -787,6 +797,6 @@ export class ApiContractPage extends BasePage {
: "<not visible>";

console.error(`Prerequisite error summary: ${summaryText}`);
console.error(`Prerequisite error detail: ${detailedMessage}`);
console.error(`Prerequisite error detail: ${detailedMessage}`);
}
}
145 changes: 145 additions & 0 deletions page-objects/service-spec-config-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,151 @@ export class ServiceSpecConfigPage extends BasePage {
});
}

async editSpecInEditor(searchText: string, replaceText: string) {
await test.step(`Edit spec in editor: '${searchText}' -> '${replaceText}'`, async () => {
const content = this.specSection.locator(".cm-content").first();
const scroller = this.specSection.locator(".cm-scroller").first();
const lines = this.specSection.locator(".cm-content .cm-line");

await expect(content).toBeVisible({ timeout: 10000 });

// Phase 1: scroll .cm-scroller to bottom so CodeMirror renders all lines
await this.loadFullEditorContent(scroller);
await scroller.evaluate((el) => { el.scrollTop = 0; });
await this.page.waitForTimeout(200);

// Phase 2: try the CodeMirror API to jump directly to the term
const foundByApi = await this.scrollToTextViaCodeMirrorApi(content, searchText);

// Phase 3: if the API didn't work, manually scroll to find the line
if (!foundByApi) {
await this.scrollEditorToFindText(content, scroller, lines, searchText);
}

const targetLine = lines.filter({ hasText: searchText }).first();
await expect(targetLine).toBeVisible({ timeout: 10000 });

const originalText = await targetLine.innerText();
const leadingSpaces = originalText.match(/^\s*/)?.[0] ?? "";

await targetLine.scrollIntoViewIfNeeded();
await targetLine.click();

// Home twice: first Home moves past soft indent, second goes to column 0
await this.page.keyboard.press("Home");
await this.page.keyboard.press("Home");
await this.page.keyboard.press("Shift+End");
await this.page.keyboard.press("Backspace");
await this.page.keyboard.type(leadingSpaces + replaceText.trim());

const safeFileName = replaceText.trim().replace(/[^a-zA-Z0-9]/g, "-");
await takeAndAttachScreenshot(
this.page,
`spec-editor-edit-${safeFileName}`,
this.eyes,
);
});
}

private async loadFullEditorContent(scroller: Locator): Promise<void> {
await expect(scroller).toBeVisible({ timeout: 10000 });

let unchangedCount = 0;
let previousScrollHeight = -1;

for (let i = 0; i < 60; i++) {
const metrics = await scroller.evaluate((el) => {
el.scrollTop = el.scrollHeight;
return {
scrollTop: el.scrollTop,
scrollHeight: el.scrollHeight,
clientHeight: el.clientHeight,
};
});

if (metrics.scrollHeight === previousScrollHeight) {
unchangedCount += 1;
} else {
unchangedCount = 0;
}

previousScrollHeight = metrics.scrollHeight;

const atBottom =
metrics.scrollTop + metrics.clientHeight >= metrics.scrollHeight - 2;
if (atBottom && unchangedCount >= 2) {
break;
}

await this.page.waitForTimeout(120);
}
}

private async scrollToTextViaCodeMirrorApi(
content: Locator,
searchText: string,
): Promise<boolean> {
return await content.evaluate((el, term) => {
const cmEditor = el.closest(".cm-editor") as any;
const view = cmEditor?.cmView?.view;
if (!view) return false;

const fullText = view.state.doc.toString() as string;
const index = fullText.indexOf(term);
if (index === -1) return false;

view.dispatch({
selection: { anchor: index, head: index + term.length },
scrollIntoView: true,
});
return true;
}, searchText);
}

private async scrollEditorToFindText(
content: Locator,
scroller: Locator,
lines: Locator,
searchText: string,
): Promise<void> {
await scroller.evaluate((el) => { el.scrollTop = 0; });
await this.page.waitForTimeout(120);

for (let i = 0; i < 250; i++) {
const match = lines.filter({ hasText: searchText }).first();
if ((await match.count()) > 0) {
await match.scrollIntoViewIfNeeded();
return;
}

const moved = await scroller.evaluate((el) => {
const prev = el.scrollTop;
el.scrollTop = Math.min(
el.scrollTop + Math.max(el.clientHeight * 0.85, 120),
el.scrollHeight,
);
return el.scrollTop > prev;
});

if (!moved) break;
await this.page.waitForTimeout(80);
}

// Fallback: hover and mouse-wheel scroll
await content.hover();
for (let i = 0; i < 280; i++) {
const visible = await lines.evaluateAll(
(els, term) => els.some((el) => el.textContent?.includes(term)),
searchText,
);
if (visible) return;
await this.page.mouse.wheel(0, 900);
await this.page.waitForTimeout(60);
}

throw new Error(`Could not find '${searchText}' in the spec editor`);
}

async deleteSpecLinesInEditor(searchText: string, lineCount: number = 1) {
await test.step(`Delete ${lineCount} spec line(s) starting with '${searchText}'`, async () => {
const lines = this.specSection.locator(".cm-content .cm-line");
Expand Down
Loading
Loading