Skip to content

Commit 7ade331

Browse files
Merge pull request #25 from specmatic/addBCCInNewlyInlinedSpec
Add bcc in newly inlined spec . Added Backward Compatible and Incompatible tests.
2 parents 9dd1757 + 5fa7c8b commit 7ade331

19 files changed

+873
-550
lines changed

page-objects/api-contract-page.ts

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ export class ApiContractPage extends BasePage {
7171

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

74+
readonly infoDialog: Locator;
75+
7476
constructor(page: Page, testInfo: TestInfo, eyes: any, specName: string) {
7577
super(page, testInfo, eyes, specName);
7678
this.specTree = page.locator("#spec-tree");
@@ -218,6 +220,8 @@ export class ApiContractPage extends BasePage {
218220
this.includeButton = this.specSection
219221
.locator("button.clear")
220222
.filter({ hasText: "Include" });
223+
224+
this.infoDialog = this.page.locator("#alert-container .alert-msg.info");
221225
}
222226

223227
//Function Beginning
@@ -247,7 +251,7 @@ export class ApiContractPage extends BasePage {
247251
);
248252
}
249253

250-
async clickRunContractTests() {
254+
async clickRunContractTests(expectSuccess: boolean = true) {
251255
await test.step("Run Contract Tests", async () => {
252256
try {
253257
await expect(this._runButton).toBeEnabled({ timeout: 10000 });
@@ -260,19 +264,47 @@ export class ApiContractPage extends BasePage {
260264

261265
await takeAndAttachScreenshot(this.page, "clicked-run-contract-tests");
262266

263-
await this.waitForTestCompletion();
267+
if (expectSuccess) {
268+
await this.waitForTestCompletion();
269+
} else {
270+
await this.page.waitForTimeout(1000);
271+
}
264272
} catch (e) {
265273
await takeAndAttachScreenshot(this.page, "error-in-run-contract-tests");
266274
throw new Error(`Failed to click Run button:`);
267275
}
268276
});
269277
}
270278

279+
async waitforDialogToDismiss(status: string | RegExp) {
280+
try {
281+
const appeared = await this.infoDialog
282+
.waitFor({ state: "visible", timeout: 5000 })
283+
.then(() => true)
284+
.catch(() => false);
285+
286+
if (!appeared) {
287+
console.log("[INFO] Dialog did not appear — safe to continue");
288+
return;
289+
}
290+
291+
await expect.soft(this.infoDialog).toContainText(status, {
292+
timeout: 10000,
293+
});
294+
295+
await this.infoDialog.waitFor({ state: "hidden", timeout: 5000 });
296+
} catch (e) {
297+
console.log("[WARN] Dialog wait issue — continuing:", e);
298+
}
299+
}
300+
271301
private async waitForTestCompletion() {
272302
await this.waitForTestsToStartRunning();
273303

274304
await this.waitForTestsToCompleteExecution();
275305

306+
await this.waitforDialogToDismiss(/Tests? complete/i);
307+
276308
await takeAndAttachScreenshot(this.page, "test-completed", this.eyes);
277309
}
278310

@@ -376,6 +408,7 @@ export class ApiContractPage extends BasePage {
376408
path: string,
377409
method: string,
378410
response: string,
411+
{ captureVisual = true }: { captureVisual?: boolean } = {},
379412
) {
380413
const checkbox = this.exclusionCheckboxLocator(path, method, response);
381414
try {
@@ -400,11 +433,13 @@ export class ApiContractPage extends BasePage {
400433
if (!isChecked) {
401434
await checkbox.click();
402435
}
403-
await takeAndAttachScreenshot(
404-
this.page,
405-
"excluded-test-" + `${path}-${method}-${response}`,
406-
this.eyes,
407-
);
436+
if (captureVisual) {
437+
await takeAndAttachScreenshot(
438+
this.page,
439+
"excluded-test-" + `${path}-${method}-${response}`,
440+
this.eyes,
441+
);
442+
}
408443
}
409444

410445
async clickExcludeButton() {
@@ -442,8 +477,14 @@ export class ApiContractPage extends BasePage {
442477
testItem.path,
443478
testItem.method,
444479
testItem.response,
480+
{ captureVisual: false },
445481
);
446482
}
483+
await takeAndAttachScreenshot(
484+
this.page,
485+
`multiple-tests-selected-${testList.length}`,
486+
this.eyes,
487+
);
447488
}
448489

449490
async getMixedOperationErrorText(): Promise<string> {
@@ -762,7 +803,9 @@ export class ApiContractPage extends BasePage {
762803
const summary = this.getPrereqErrorSummary();
763804
const message = this.getPrereqErrorMessage();
764805

765-
const isVisible = await summary.isVisible({ timeout: 3000 }).catch(() => false);
806+
const isVisible = await summary
807+
.isVisible({ timeout: 3000 })
808+
.catch(() => false);
766809
if (!isVisible) {
767810
console.log("No prerequisite error detected.");
768811
return;
@@ -775,9 +818,13 @@ export class ApiContractPage extends BasePage {
775818

776819
await summary.click();
777820

778-
await expect(message).toBeVisible({ timeout: 5000 }).catch(() => {
779-
console.warn("Detail message did not become visible after clicking summary");
780-
});
821+
await expect(message)
822+
.toBeVisible({ timeout: 5000 })
823+
.catch(() => {
824+
console.warn(
825+
"Detail message did not become visible after clicking summary",
826+
);
827+
});
781828

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

@@ -787,6 +834,6 @@ export class ApiContractPage extends BasePage {
787834
: "<not visible>";
788835

789836
console.error(`Prerequisite error summary: ${summaryText}`);
790-
console.error(`Prerequisite error detail: ${detailedMessage}`);
837+
console.error(`Prerequisite error detail: ${detailedMessage}`);
791838
}
792839
}

page-objects/example-generation-page.ts

Lines changed: 15 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
import { takeAndAttachScreenshot } from "../utils/screenshotUtils";
1212
import { BasePage } from "./base-page";
1313
import { Edit } from "../utils/types/json-edit.types";
14+
import { SpecEditorPage } from "./spec-editor-page";
15+
1416

1517
export class ExampleGenerationPage extends BasePage {
1618
readonly openApiTabPage: OpenAPISpecTabPage;
@@ -30,6 +32,8 @@ export class ExampleGenerationPage extends BasePage {
3032
private readonly specSection: Locator;
3133
private readonly specEditorSection: Locator;
3234
private readonly specTabLocator: Locator;
35+
private readonly specEditorHelper: SpecEditorPage;
36+
3337

3438
constructor(page: Page, testInfo: TestInfo, eyes: any, specName: string) {
3539
super(page, testInfo, eyes, specName);
@@ -62,8 +66,10 @@ export class ExampleGenerationPage extends BasePage {
6266
this.bulkValidateBtnSelector = "button#bulk-validate";
6367
this.inlineBtnSelector = "button#import";
6468
this.bulkFixBtnSelector = "button#bulk-fix";
69+
this.specEditorHelper = new SpecEditorPage(page);
6570
}
6671

72+
6773
private async openExampleGenerationTab() {
6874
console.log("Opening Example Generation tab");
6975
return this.openApiTabPage.openExampleGenerationTab();
@@ -1134,7 +1140,7 @@ export class ExampleGenerationPage extends BasePage {
11341140
await expect(editorContext.content).toBeVisible({ timeout: 15000 });
11351141
await editorContext.content.click();
11361142

1137-
await this.loadFullEditorDocument(editorContext.scroller);
1143+
await this.specEditorHelper.loadFullEditorDocument(editorContext.scroller);
11381144
await editorContext.scroller.evaluate((el) => {
11391145
el.scrollTop = 0;
11401146
});
@@ -1145,7 +1151,7 @@ export class ExampleGenerationPage extends BasePage {
11451151
: [`${exampleName}_response`];
11461152

11471153
for (const searchTerm of targets) {
1148-
const foundByEditorApi = await this.focusTermUsingCodeMirrorApi(
1154+
const foundByEditorApi = await this.specEditorHelper.focusTermUsingCodeMirrorApi(
11491155
editorContext.content,
11501156
searchTerm,
11511157
);
@@ -1157,12 +1163,18 @@ export class ExampleGenerationPage extends BasePage {
11571163
);
11581164

11591165
if (!foundByWindowFind) {
1160-
await this.scrollToEditorSearchTerm(
1166+
await this.specEditorHelper.scrollEditorToFindTerm(
11611167
editorContext.content,
11621168
editorContext.scroller,
11631169
editorContext.lines,
11641170
searchTerm,
11651171
);
1172+
// Click the matched line for visual cursor placement
1173+
const match = editorContext.lines.filter({ hasText: searchTerm }).first();
1174+
if ((await match.count()) > 0) {
1175+
await match.scrollIntoViewIfNeeded();
1176+
await match.click();
1177+
}
11661178
}
11671179

11681180
await this.page.waitForTimeout(250);
@@ -1238,113 +1250,4 @@ export class ExampleGenerationPage extends BasePage {
12381250
return win.find(term, false, false, true, false, false, false);
12391251
}, searchTerm);
12401252
}
1241-
1242-
private async focusTermUsingCodeMirrorApi(
1243-
content: Locator,
1244-
searchTerm: string,
1245-
): Promise<boolean> {
1246-
return await content.evaluate((el, term) => {
1247-
const cmEditor = el.closest(".cm-editor") as any;
1248-
const view = cmEditor?.cmView?.view;
1249-
if (!view) return false;
1250-
1251-
const fullText = view.state.doc.toString() as string;
1252-
const index = fullText.indexOf(term);
1253-
if (index === -1) return false;
1254-
1255-
view.dispatch({
1256-
selection: { anchor: index, head: index + term.length },
1257-
scrollIntoView: true,
1258-
});
1259-
return true;
1260-
}, searchTerm);
1261-
}
1262-
1263-
private async loadFullEditorDocument(scroller: Locator): Promise<void> {
1264-
await expect(scroller).toBeVisible({ timeout: 10000 });
1265-
1266-
let unchangedCount = 0;
1267-
let previousScrollHeight = -1;
1268-
1269-
for (let i = 0; i < 60; i++) {
1270-
const metrics = await scroller.evaluate((el) => {
1271-
el.scrollTop = el.scrollHeight;
1272-
return {
1273-
scrollTop: el.scrollTop,
1274-
scrollHeight: el.scrollHeight,
1275-
clientHeight: el.clientHeight,
1276-
};
1277-
});
1278-
1279-
if (metrics.scrollHeight === previousScrollHeight) {
1280-
unchangedCount += 1;
1281-
} else {
1282-
unchangedCount = 0;
1283-
}
1284-
1285-
previousScrollHeight = metrics.scrollHeight;
1286-
1287-
const atBottom =
1288-
metrics.scrollTop + metrics.clientHeight >= metrics.scrollHeight - 2;
1289-
if (atBottom && unchangedCount >= 2) {
1290-
break;
1291-
}
1292-
1293-
await this.page.waitForTimeout(120);
1294-
}
1295-
}
1296-
1297-
private async scrollToEditorSearchTerm(
1298-
content: Locator,
1299-
scroller: Locator,
1300-
lines: Locator,
1301-
searchTerm: string,
1302-
): Promise<void> {
1303-
await scroller.evaluate((el) => {
1304-
el.scrollTop = 0;
1305-
});
1306-
await this.page.waitForTimeout(120);
1307-
1308-
for (let i = 0; i < 250; i++) {
1309-
const match = lines.filter({ hasText: searchTerm }).first();
1310-
const count = await match.count();
1311-
1312-
if (count > 0) {
1313-
await match.scrollIntoViewIfNeeded();
1314-
await match.click();
1315-
return;
1316-
}
1317-
1318-
const moved = await scroller.evaluate((el) => {
1319-
const prev = el.scrollTop;
1320-
el.scrollTop = Math.min(
1321-
el.scrollTop + Math.max(el.clientHeight * 0.85, 120),
1322-
el.scrollHeight,
1323-
);
1324-
return el.scrollTop > prev;
1325-
});
1326-
1327-
if (!moved) {
1328-
break;
1329-
}
1330-
1331-
await this.page.waitForTimeout(80);
1332-
}
1333-
1334-
// Fallback: some editor layouts scroll via outer containers, not .cm-scroller.
1335-
await content.hover();
1336-
for (let i = 0; i < 280; i++) {
1337-
const visible = await lines.evaluateAll(
1338-
(els, term) => els.some((el) => el.textContent?.includes(term)),
1339-
searchTerm,
1340-
);
1341-
if (visible) {
1342-
return;
1343-
}
1344-
await this.page.mouse.wheel(0, 900);
1345-
await this.page.waitForTimeout(60);
1346-
}
1347-
1348-
throw new Error(`Could not find '${searchTerm}' in the spec editor`);
1349-
}
13501253
}

0 commit comments

Comments
 (0)