Skip to content

Commit 74bca69

Browse files
committed
feat: Added test for bcc compatible and incompatible changes in a single loop and assert the error count.
1 parent 3c1b47f commit 74bca69

File tree

3 files changed

+220
-99
lines changed

3 files changed

+220
-99
lines changed

specs/openapi/update-service-spec/backward-compatibility-test.spec.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ import { PRODUCT_SEARCH_BFF_SPEC_BACKWARD_COMPATIBILITY } from "../../specNames"
33
import { ServiceSpecConfigPage } from "../../../page-objects/service-spec-config-page";
44
import { Page } from "playwright/test";
55

6+
const SCENARIOS = [
7+
{
8+
oldText: "summary: Create a new product",
9+
newText: "summary: Create product",
10+
expectedMessage: "Changes are backward compatible",
11+
},
12+
];
13+
614
test.describe("API Specification", () => {
715
test(
816
"Backward Compatibility Test",
@@ -11,13 +19,10 @@ test.describe("API Specification", () => {
1119
const configPage = await setupConfigPage(page, testInfo, eyes);
1220

1321
await test.step("Remove summary field from /products endpoint and save", async () => {
14-
await configPage.deleteSpecLinesInEditor(
15-
"summary: Create a new product",
16-
1,
17-
);
22+
for (const scenario of SCENARIOS) {
23+
await configPage.verifyCompatibilityScenario(scenario);
24+
}
1825
});
19-
20-
await assertDialog(configPage, page);
2126
},
2227
);
2328
});

specs/openapi/update-service-spec/backward-incompatible-test.spec.ts

Lines changed: 56 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -3,69 +3,65 @@ import { PRODUCT_SEARCH_BFF_SPEC_BACKWARD_INCOMPATIBLE } from "../../specNames";
33
import { ServiceSpecConfigPage } from "../../../page-objects/service-spec-config-page";
44
import { Page } from "@playwright/test";
55

6-
test.describe("API Specification — Backward Incompatibility", () => {
7-
test(
8-
"Adding path parameter to existing endpoint to check backward compatibility",
9-
{ tag: ["@spec", "@bccIncompatibleTest", "@eyes"] },
10-
async ({ page, eyes }, testInfo) => {
11-
const configPage = await setupConfigPage(page, testInfo, eyes);
12-
13-
await test.step("Change /products to /products/{id} in the editor", async () => {
14-
await configPage.editSpecInEditor(" /products:", " /products/{id}:");
15-
});
16-
17-
await assertBccFailure(
18-
configPage,
19-
"This API exists in the old contract but not in the new contract",
20-
1,
21-
);
22-
},
23-
);
6+
const INCOMPATIBLE_SCENARIOS = [
7+
{
8+
name: "Add path parameter to existing endpoint",
9+
oldText: " /products:",
10+
newText: " /products/{id}:",
11+
lineCount: 0,
12+
expectedErrorCount: 1,
13+
expectedDetail:
14+
"This API exists in the old contract but not in the new contract",
15+
isExpectedFailure: false,
16+
},
17+
{
18+
name: "Remove a response status code",
19+
oldText: " '201':",
20+
newText: " '299':",
21+
lineCount: 0,
22+
expectedErrorCount: 1,
23+
expectedDetail:
24+
"This API exists in the old contract but not in the new contract",
25+
isExpectedFailure: false,
26+
},
27+
{
28+
name: "Change optional parameter to required",
29+
oldText: " required: false",
30+
newText: " required: true",
31+
lineCount: 0,
32+
expectedErrorCount: 1,
33+
expectedDetail:
34+
'New specification expects query param "type" in the request',
35+
isExpectedFailure: false,
36+
},
37+
{
38+
name: "Remove all content from requestBody",
39+
oldText: " content:",
40+
newText: "",
41+
lineCount: 4,
42+
expectedErrorCount: 1,
43+
expectedDetail:
44+
"This is no body in the new specification, but json object in the old specification",
45+
isExpectedFailure: false,
46+
},
47+
];
2448

25-
test(
26-
"Removing a response status code is a backward incompatible change",
27-
{ tag: ["@spec", "@bccIncompatibleTest", "@eyes", "@expected-failure"] },
28-
async ({ page, eyes }, testInfo) => {
29-
test.fail(
30-
true,
31-
"Error count does not match with acutal error. Needs fixing by dev",
32-
);
33-
const configPage = await setupConfigPage(page, testInfo, eyes);
34-
35-
await test.step("Change response status code '201' to '299' under /products in the editor", async () => {
36-
await configPage.editSpecInEditor(" '201':", " '299':");
37-
});
49+
test.describe("API Specification — Backward Incompatibility", () => {
50+
let configPage: ServiceSpecConfigPage;
3851

39-
await assertBccFailure(
40-
configPage,
41-
"This API exists in the old contract but not in the new contract",
42-
2,
43-
);
44-
},
45-
);
52+
test.beforeEach(async ({ page, eyes }, testInfo) => {
53+
configPage = await setupConfigPage(page, testInfo, eyes);
54+
});
4655

4756
test(
48-
"Making a required parameter optional is a backward incompatible change",
49-
{ tag: ["@spec", "@bccIncompatibleTest", "@eyes", "@expected-failure"] },
50-
async ({ page, eyes }, testInfo) => {
51-
test.fail(
52-
true,
53-
"Error count does not match with acutal error. Needs fixing by dev",
54-
);
55-
const configPage = await setupConfigPage(page, testInfo, eyes);
56-
57-
await test.step("Making optional Parameter required", async () => {
58-
await configPage.editSpecInEditor(
59-
" required: false",
60-
" required: true",
61-
);
62-
});
63-
64-
await assertBccFailure(
65-
configPage,
66-
'New specification expects query param "type" in the request but it is missing from the old specification',
67-
3,
68-
);
57+
"Run all incompatibility scenarios in one session",
58+
{ tag: ["@spec", "@bccIncompatibleTest", "@eyes"] },
59+
async () => {
60+
for (const scenario of INCOMPATIBLE_SCENARIOS) {
61+
await test.step(`Testing scenario: ${scenario.name}`, async () => {
62+
await configPage.verifyIncompatibilityScenario(scenario);
63+
});
64+
}
6965
},
7066
);
7167
});
@@ -77,39 +73,6 @@ async function setupConfigPage(page: Page, testInfo: any, eyes: any) {
7773
eyes,
7874
PRODUCT_SEARCH_BFF_SPEC_BACKWARD_INCOMPATIBLE,
7975
);
80-
await test.step(`Go to Spec page for Service Spec: '${PRODUCT_SEARCH_BFF_SPEC_BACKWARD_INCOMPATIBLE}'`, async () => {
81-
await configPage.gotoHomeAndOpenSidebar();
82-
await configPage.sideBar.selectSpec(
83-
PRODUCT_SEARCH_BFF_SPEC_BACKWARD_INCOMPATIBLE,
84-
);
85-
await configPage.openSpecTab();
86-
});
87-
return configPage;
88-
}
8976

90-
async function assertBccFailure(
91-
configPage: ServiceSpecConfigPage,
92-
expectedErrorDetail: string,
93-
expectedErrorCount: number,
94-
) {
95-
await test.step("Run Backward Compatibility test and assert failure toast", async () => {
96-
await configPage.runBackwardCompatibilityTest();
97-
const toastText = await configPage.getAlertMessageText();
98-
expect.soft(toastText).toContain("Backward compatibility test failed");
99-
await configPage.dismissAlert();
100-
});
101-
102-
await test.step("Assert error dropdown heading shows 1 error", async () => {
103-
await configPage.toggleBccErrorSection(true);
104-
const { summary } = await configPage.getBccErrorDetails();
105-
expect
106-
.soft(summary)
107-
.toContain(`Backward Compatibility found ${expectedErrorCount} error`);
108-
});
109-
110-
await test.step("Assert error detail describes the contract mismatch", async () => {
111-
const { details } = await configPage.getBccErrorDetails();
112-
expect.soft(details.length).toBeGreaterThan(0);
113-
expect.soft(details[0]).toContain(expectedErrorDetail);
114-
});
77+
return configPage;
11578
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { test, expect } from "../../../utils/eyesFixture";
2+
import { PRODUCT_SEARCH_BFF_SPEC_BACKWARD_INCOMPATIBLE } from "../../specNames";
3+
import { ServiceSpecConfigPage } from "../../../page-objects/service-spec-config-page";
4+
5+
interface MixedScenario {
6+
name: string;
7+
originalText: string;
8+
newText: string;
9+
removeXLinesFromSpec: number;
10+
isCompatible: boolean;
11+
expectedErrorCount: number;
12+
expectedErrorDetail: string;
13+
}
14+
15+
interface ScenarioGroup {
16+
groupName: string;
17+
scenarios: MixedScenario[];
18+
}
19+
20+
const MIXED_SCENARIO_GROUPS: ScenarioGroup[] = [
21+
{
22+
groupName: "Compatible then Incompatible — Edit-based changes",
23+
scenarios: [
24+
{
25+
name: "Rename summary (Compatible)",
26+
originalText: "summary: Create a new product",
27+
newText: "summary: Create Product",
28+
removeXLinesFromSpec: 0,
29+
isCompatible: true,
30+
expectedErrorCount: 0,
31+
expectedErrorDetail: "",
32+
},
33+
{
34+
name: "Change response code (Incompatible)",
35+
originalText: "'201':",
36+
newText: "'299':",
37+
removeXLinesFromSpec: 0,
38+
isCompatible: false,
39+
expectedErrorCount: 1,
40+
expectedErrorDetail: "This API exists in the old contract",
41+
},
42+
{
43+
name: "Remove requestBody content (Incompatible)",
44+
originalText: " content:",
45+
newText: "",
46+
removeXLinesFromSpec: 4,
47+
isCompatible: false,
48+
expectedErrorCount: 2,
49+
expectedErrorDetail:
50+
"This is no body in the new specification, but json object in the old specification",
51+
},
52+
],
53+
},
54+
{
55+
groupName: "Making optional Parameter Required",
56+
scenarios: [
57+
{
58+
name: "Change optional parameter to required",
59+
originalText: " required: false",
60+
newText: " required: true",
61+
removeXLinesFromSpec: 0,
62+
isCompatible: false,
63+
expectedErrorCount: 1,
64+
expectedErrorDetail:
65+
'New specification expects query param "type" in the request',
66+
},
67+
],
68+
},
69+
];
70+
71+
test.describe("API Specification — Mixed Backward Compatibility Analysis", () => {
72+
for (const group of MIXED_SCENARIO_GROUPS) {
73+
test(
74+
`Group: ${group.groupName}`,
75+
{ tag: ["@bcc", "@mixed"] },
76+
async ({ page, eyes }, testInfo) => {
77+
const configPage = new ServiceSpecConfigPage(
78+
page,
79+
testInfo,
80+
eyes,
81+
PRODUCT_SEARCH_BFF_SPEC_BACKWARD_INCOMPATIBLE,
82+
);
83+
84+
await test.step("Navigate to Spec Editor", async () => {
85+
await configPage.gotoHomeAndOpenSidebar();
86+
await configPage.sideBar.selectSpec(
87+
PRODUCT_SEARCH_BFF_SPEC_BACKWARD_INCOMPATIBLE,
88+
);
89+
await configPage.openSpecTab();
90+
});
91+
92+
for (const scenario of group.scenarios) {
93+
await applyScenarioChange(configPage, scenario);
94+
await assertScenarioResult(configPage, scenario);
95+
}
96+
},
97+
);
98+
}
99+
});
100+
101+
102+
async function applyScenarioChange(
103+
configPage: ServiceSpecConfigPage,
104+
scenario: MixedScenario,
105+
) {
106+
await test.step(`Apply Change: ${scenario.name}`, async () => {
107+
if (scenario.removeXLinesFromSpec > 0) {
108+
await configPage.deleteSpecLinesInEditor(
109+
scenario.originalText,
110+
scenario.removeXLinesFromSpec,
111+
);
112+
} else if (scenario.newText !== "") {
113+
await configPage.editSpecInEditor(
114+
scenario.originalText,
115+
scenario.newText,
116+
);
117+
}
118+
});
119+
}
120+
121+
async function assertScenarioResult(
122+
configPage: ServiceSpecConfigPage,
123+
scenario: MixedScenario,
124+
) {
125+
await test.step(`Assert Result: ${scenario.name}`, async () => {
126+
await configPage.runBackwardCompatibilityTest();
127+
128+
const toastText = await configPage.getAlertMessageText();
129+
130+
if (scenario.isCompatible) {
131+
expect(toastText.toLowerCase()).toContain("backward compatible");
132+
await configPage.dismissAlert();
133+
} else {
134+
expect(toastText.toLowerCase()).toContain("failed");
135+
await configPage.dismissAlert();
136+
137+
await configPage.toggleBccErrorSection(true);
138+
const { summary, details } = await configPage.getBccErrorDetails();
139+
140+
expect.soft(summary).toContain(`${scenario.expectedErrorCount} error`);
141+
142+
const hasMatch = details.some((d: string) =>
143+
d.includes(scenario.expectedErrorDetail),
144+
);
145+
expect
146+
.soft(
147+
hasMatch,
148+
`Expected detail not found: ${scenario.expectedErrorDetail}`,
149+
)
150+
.toBe(true);
151+
}
152+
});
153+
}

0 commit comments

Comments
 (0)