Skip to content

Commit 5234062

Browse files
pranay-tippaPranay Tippa
andauthored
test: Replace a Form Component validation (#149)
Co-authored-by: Pranay Tippa <ptippa@adobe.com>
1 parent bd38ed5 commit 5234062

6 files changed

Lines changed: 153 additions & 58 deletions

File tree

.circleci/config.yml

Lines changed: 12 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,7 @@ orbs:
44
node: circleci/node@5
55
browser-tools: circleci/browser-tools@1.2.3
66

7-
# ------------------------------------------------
8-
# JOB DEFINITIONS
9-
# ------------------------------------------------
107
jobs:
11-
# ---------------- LINT JOB ----------------
128
lint:
139
executor: node/default
1410
steps:
@@ -19,7 +15,6 @@ jobs:
1915
name: Run lint
2016
command: npm run lint
2117

22-
# ---------------- UNIT TEST JOB ----------------
2318
unit-tests:
2419
executor: node/default
2520
steps:
@@ -29,7 +24,10 @@ jobs:
2924
- run:
3025
name: Run unit tests with coverage
3126
command: |
32-
npx c8 --reporter=json --reporter=text --lines 85 npm run test:unit || true
27+
set +e
28+
npx c8 --reporter=json --reporter=text --lines 85 npm run test:unit
29+
TEST_EXIT=$?
30+
set -e
3331
3432
if [ -f "coverage/coverage-final.json" ]; then
3533
mv coverage/coverage-final.json coverage/coverage-final-unit.json
@@ -43,8 +41,8 @@ jobs:
4341
mkdir -p coverage
4442
echo '{}' > coverage/coverage-final-unit.json
4543
fi
46-
environment:
47-
CI: true
44+
45+
exit $TEST_EXIT
4846
4947
- store_artifacts:
5048
path: coverage/unit-artifacts
@@ -55,26 +53,25 @@ jobs:
5553
paths:
5654
- coverage/coverage-final-unit.json
5755

58-
# ---------------- E2E TEST JOB ----------------
5956
e2e-tests:
6057
docker:
6158
- image: mcr.microsoft.com/playwright:v1.40.0-jammy
6259
steps:
6360
- checkout
64-
6561
- run:
6662
name: Install dependencies
6763
command: npm ci
68-
6964
- run:
7065
name: Install Playwright browsers
7166
command: npx playwright install chromium
72-
7367
- run:
7468
name: Run E2E tests with coverage
7569
command: |
70+
set +e
7671
npx c8 --reporter=json --reporter=text --lines 45 \
77-
npx playwright test --project='chromium' || true
72+
npx playwright test --project='chromium'
73+
TEST_EXIT=$?
74+
set -e
7875
7976
if [ -f "coverage/coverage-final.json" ]; then
8077
mv coverage/coverage-final.json coverage/coverage-final-e2e.json
@@ -88,18 +85,15 @@ jobs:
8885
mkdir -p coverage
8986
echo '{}' > coverage/coverage-final-e2e.json
9087
fi
91-
environment:
92-
CI: true
93-
COVERAGE_RUN: true
88+
89+
exit $TEST_EXIT
9490
9591
- store_artifacts:
9692
path: test/e2e/reports
9793
destination: test-results
98-
9994
- store_artifacts:
10095
path: videos
10196
destination: videos
102-
10397
- store_artifacts:
10498
path: test-results
10599
destination: screenshots
@@ -109,7 +103,6 @@ jobs:
109103
paths:
110104
- coverage/coverage-final-e2e.json
111105

112-
# ---------------- MERGE + REPORT COVERAGE ----------------
113106
check-coverage:
114107
executor: node/default
115108
steps:
@@ -118,7 +111,6 @@ jobs:
118111
pkg-manager: npm
119112
- attach_workspace:
120113
at: .
121-
122114
- run:
123115
name: Merge and generate final coverage
124116
command: |
@@ -127,7 +119,6 @@ jobs:
127119
UNIT_FILE="coverage/coverage-final-unit.json"
128120
E2E_FILE="coverage/coverage-final-e2e.json"
129121
130-
# Normalize paths (needed for correct merging)
131122
if [ -f "$UNIT_FILE" ]; then
132123
sed 's|"/home/circleci/project/|"/project/|g' "$UNIT_FILE" > coverage/normalized/unit.json
133124
fi
@@ -136,20 +127,16 @@ jobs:
136127
sed 's|"/root/project/|"/project/|g' "$E2E_FILE" > coverage/normalized/e2e.json
137128
fi
138129
139-
# Merge
140130
npx nyc merge coverage/normalized .nyc_output/coverage.json
141131
142-
# Convert back so NYC can resolve sources
143132
sed 's|"/project/|"/home/circleci/project/|g' .nyc_output/coverage.json > .nyc_output/coverage-final.json
144133
mv .nyc_output/coverage-final.json .nyc_output/coverage.json
145134
146-
# Generate final combined coverage reports
147135
mkdir -p coverage/combined-artifacts
148136
npx nyc report --reporter=html --report-dir=coverage/combined-artifacts
149137
npx nyc report --reporter=text-summary > coverage/combined-artifacts/combined-text-summary.txt
150138
cp coverage/combined-artifacts/index.html coverage/combined-artifacts/indexCombined.html
151139
152-
# Enforce coverage thresholds
153140
npx nyc report --reporter=lcov --report-dir=coverage/combined-artifacts
154141
npx nyc report --check-coverage --lines=90 --functions=85 --branches=90
155142
@@ -161,9 +148,6 @@ jobs:
161148
path: coverage
162149
destination: all-coverage-files
163150

164-
# ------------------------------------------------
165-
# WORKFLOWS
166-
# ------------------------------------------------
167151
workflows:
168152
lint:
169153
jobs:

test/e2e/main/page/universalEditorBasePage.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ export class UniversalEditorBase {
77
ruleEditor: 'button[aria-label="Rule Editor"]',
88
preview: '[aria-label="Preview"]',
99
contentTree: 'button[aria-label="Content tree"]',
10+
dataSource: 'button[aria-label="Data Sources"]',
1011
mainInContentTree: 'li > [class*="content expandable collapsed"]',
1112
adaptiveFormPathInUE: 'main[class="Canvas"] button[data-resource$="content/root/section/form"]',
1213
adaptiveFormDropdown: 'li[data-resource*="content/root/section/form"] button[aria-label]',
14+
adaptiveFormInContentTree: 'li[data-resource*="content/root/section/form"] label[title="Adaptive Form"]',
1315
componentPath: 'div[class="form block edit-mode"] [data-aue-resource*="/',
1416
componentSelectorValidation: 'li[data-resource*="/textinput"] [class="node-content selected"]',
1517
insertComponent: 'div[data-testid="right-rail-tools"] button[aria-haspopup]',
@@ -18,6 +20,8 @@ export class UniversalEditorBase {
1820
sectionTwoPath: 'li[data-resource*="content/root/section"] div[class*="content expandable"]',
1921
defaultAndBlockMenu: 'div[role="presentation"][class*="Submenu-wrapper"]',
2022
adaptiveFormPathInBlockMenu: 'div[role="presentation"] div[data-key="blocks_form"]',
23+
formReplace: 'button[aria-label="Forms Replace"]',
24+
replaceFrameLocator: 'iframe[name="uix-guest-Forms"]',
2125
iFrame: 'iframe[name="Main Content"]',
2226
iFrameEditor: 'iframe[title="Editable app frame"]',
2327
iFrameInPreview: 'iframe[class="penpal"]',
@@ -27,8 +31,20 @@ export class UniversalEditorBase {
2731
deleteButton: 'button[aria-label="Delete"]',
2832
deleteConfirmationButton: '[data-variant="negative"][class*="aaz5ma_spectrum-ButtonGroup-Button"]',
2933
deletePopup: 'section[class*="spectrum-Dialog--destructive"]',
34+
replaceTextLocator: 'div[role="presentation"] input[type="text"]'
3035
};
3136

37+
datasource = {
38+
expandAllButton : 'button[type="button"][aria-label="Expand All"]',
39+
addButton : 'button[type="button"] span:has-text("Add")',
40+
bindRef: 'label:has-text("Bind Reference")',
41+
dataSourceFrame : 'iframe[name*="AEM Forms Datasource"]',
42+
datasourceIFrame: 'div[id="datasource"] iframe[id*="datasource"]',
43+
bindRefInput: 'div[id="datasource"] input[type="text"]',
44+
bindRefSelectButton: 'button[type="button"][aria-label="Select Bindref from Tree"]',
45+
selectButton : 'button[type="button"] span:has-text("Select")',
46+
}
47+
3248
componentUtils = new ComponentUtils();
3349
canvasUtils = new CanvasUtils();
3450

@@ -71,4 +87,23 @@ export class UniversalEditorBase {
7187
}
7288
await expect(componentPathInUE).toHaveCount(0);
7389
}
90+
91+
// This function expands the tree nodes in the content tree to reach a specific field.
92+
// Do not include leaf nodes (fields) in the path that do not have an expand/collapse button.
93+
// Only intermediate nodes with expandable behavior should be part of the path.
94+
async expandContentTreeField(page, frame, path) {
95+
await this.componentUtils.verifyAndClickContentTree(frame);
96+
const nodeNames = path.split('/').filter(Boolean);
97+
for (const nodeName of nodeNames) {
98+
const expandButtonSelector = `li[data-resource$="${nodeName}"][class*="treenode"] button`;
99+
const expandButton = frame.locator(expandButtonSelector).first();
100+
await expect(expandButton).toBeVisible({ timeout: 5000 });
101+
102+
const ariaLabel = await expandButton.getAttribute('aria-label');
103+
if (ariaLabel.includes('Expand Node')) {
104+
await expandButton.click();
105+
await expect(expandButton).toHaveAttribute('aria-label', 'Collapse Node');
106+
}
107+
}
108+
}
74109
}

test/e2e/main/utils/componentUtils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export class ComponentUtils {
44
selectors = {
55
contentTreeLabel: '[aria-label="Content tree"]',
66
deleteButton: 'button[aria-label="Delete"]',
7-
componentList: 'div[role="presentation"][class$="spectrum-Submenu-wrapper"]'
7+
componentList: 'div[class*="menu"]'
88
};
99

1010
async addComponent(frame, componentName) {
Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { expect, test } from '../../fixtures.js';
22
import { UniversalEditorBase } from '../../main/page/universalEditorBasePage.js';
3+
import { ComponentUtils } from '../../main/utils/componentUtils.js';
34

45
const universalEditorBase = new UniversalEditorBase();
6+
const componentUtils = new ComponentUtils();
57
const { selectors } = universalEditorBase;
68
const fieldPath = 'content/root/section/form';
79
const componentName = 'Email Input';
@@ -18,24 +20,21 @@ test.describe('Component properties validation in UE', () => {
1820
const iframeEditor = frame.frameLocator(selectors.iFrameEditor);
1921
const componentPathInUE = iframeEditor.locator(`${selectors.componentPath}${component}"]`);
2022
const componentTitlePathInUE = componentPathInUE.filter('input');
21-
const contentTree = frame.locator(selectors.contentTree);
23+
2224

2325
await expect(frame.locator(selectors.propertyPagePath)).toBeVisible();
2426
if (!await componentPathInUE.isVisible({ timeout: 20000 })) {
2527
await page.reload();
2628
await expect(componentPathInUE).toBeVisible({ timeout: 20000 });
2729
}
28-
await expect(contentTree).toBeVisible({ timeout: 10000 });
29-
await contentTree.click();
30+
await componentUtils.verifyAndClickContentTree(frame);
3031
const componentPathInContentTree = frame.locator(`li[data-resource$="${fieldPath}/${component}"][class*="treenode"]`).first();
31-
await expandContentTreeField(page, frame, fieldPath);
32+
await universalEditorBase.expandContentTreeField(page, frame, fieldPath);
3233
await expect(componentPathInContentTree).toBeVisible();
3334
await componentPathInContentTree.scrollIntoViewIfNeeded();
3435
await componentPathInContentTree.click({ force: true });
3536
await frame.locator(selectors.propertyPagePath).click();
36-
const componentProperties = await frame.locator(selectors.panelHeaders).first();
37-
await expect(componentProperties).toBeVisible();
38-
await expect(componentProperties).toContainText(componentName);
37+
await expect(frame.locator(`.Breadcrumb span[role="none"]:has-text("${componentName}")`)).toBeVisible();
3938

4039
// Ensure property field is visible, reload if not
4140
const isPropertyVisible = frame.locator('.is-canvas [class*="TabsPanel-tabs"]').last();
@@ -52,24 +51,3 @@ test.describe('Component properties validation in UE', () => {
5251
await expect(componentTitlePathInUE).toHaveText(componentTitle, { timeout: 5000 });
5352
});
5453
});
55-
56-
// This function expands the tree nodes in the content tree to reach a specific field.
57-
// Do not include leaf nodes (fields) in the path that do not have an expand/collapse button.
58-
// Only intermediate nodes with expandable behavior should be part of the path.
59-
async function expandContentTreeField(page, frame, path) {
60-
const nodeNames = path.split('/').filter(Boolean);
61-
for (const nodeName of nodeNames) {
62-
const expandButtonSelector = `li[data-resource$="${nodeName}"][class*="treenode"] button`;
63-
const expandButton = frame.locator(expandButtonSelector).first();
64-
await expect(expandButton).toBeVisible({ timeout: 5000 });
65-
66-
const ariaLabel = await expandButton.getAttribute('aria-label');
67-
if (ariaLabel.includes('Expand Node')) {
68-
await expandButton.click();
69-
await expect(expandButton).toHaveAttribute('aria-label', 'Collapse Node');
70-
}
71-
}
72-
}
73-
74-
75-

test/e2e/x-walk/UE-sites/components.validation.authoring.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ test.describe('Forms Authoring in Universal Editor tests', () => {
2929
}
3030
await page.reload();
3131
await componentUtils.verifyAndClickContentTree(frame);
32-
await expect(frame.locator(universalEditorBase.selectors.panelHeaders)).toHaveText('Content tree');
32+
await expect(frame.locator('div[data-testid="right-rail"] h3:text-is("Content tree")')).toBeVisible({ timeout: 8000 });
3333

3434
try {
3535
await componentPathInUE.first().waitFor({ state: 'visible', timeout: 10000 });
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { expect, test } from '../../fixtures.js';
2+
import { UniversalEditorBase } from '../../main/page/universalEditorBasePage.js';
3+
import * as componentPathInUE from '@testing-library/dom';
4+
5+
const universalEditorBase = new UniversalEditorBase();
6+
const { selectors } = universalEditorBase;
7+
8+
// Component configuration
9+
const component = 'emailinput';
10+
const fieldPath = 'content/root/section/form';
11+
const componentName1 = 'Email';
12+
const componentName2 = 'Password';
13+
const partialUrl1 = 'resource/scripts/dompurify.min.js';
14+
const partialUrl2 = 'resource/blocks/form/components/password/password-hide-icon.png';
15+
const waringMessage = 'Warning: Converting to a dissimilar type may cause existing functionality to break.';
16+
17+
const testURL =
18+
'https://author-p133911-e1313554.adobeaemcloud.com/ui#/@formsinternal01/aem/universal-editor/canvas/author-p133911-e1313554.adobeaemcloud.com/content/forms/af/forms-x-walk-collateral/formsreplace.html';
19+
20+
test.describe('Forms Replace Component', () => {
21+
22+
test('Verify component replacement functionality in Forms Replace @chromium-only', async ({ page }) => {
23+
test.setTimeout(60000);
24+
await page.goto(testURL, { waitUntil: 'load' });
25+
const frame = page.frameLocator(selectors.iFrame);
26+
const iframeEditor = frame.frameLocator(selectors.iFrameEditor);
27+
const componentPathInUE = iframeEditor.locator(`${selectors.componentPath}${component}"]`);
28+
await expect(frame.locator(selectors.propertyPagePath)).toBeVisible();
29+
30+
await universalEditorBase.expandContentTreeField(page, frame, 'content/root/section');
31+
try {
32+
await expect(frame.locator(`li[data-resource$="${fieldPath}"][class*="treenode"] button`)).toBeVisible({timeout: 6000});
33+
} catch (e) {
34+
await page.reload();
35+
await componentPathInUE.waitFor({ state: 'visible', timeout: 20000 });
36+
}
37+
await universalEditorBase.expandContentTreeField(page, frame, fieldPath);
38+
const componentPathInContentTree = frame.locator(`li[data-resource$="${fieldPath}/${component}"][class*="treenode"]`).first();
39+
await frame.locator(universalEditorBase.selectors.adaptiveFormInContentTree).locator('span').click();
40+
await expect(frame.locator(universalEditorBase.selectors.adaptiveFormInContentTree).locator('..')).toHaveAttribute('aria-selected', 'true');
41+
await expect(componentPathInContentTree).toBeVisible();
42+
await componentPathInContentTree.scrollIntoViewIfNeeded();
43+
await componentPathInContentTree.click({ force: true });
44+
const formReplaceButton = frame.locator(selectors.formReplace);
45+
await expect(formReplaceButton).toBeVisible({ timeout: 8000 });
46+
await formReplaceButton.click();
47+
const replaceIframe = frame.frameLocator(universalEditorBase.selectors.replaceFrameLocator);
48+
await expect(replaceIframe.locator('div[id="forms-replace"] div span:has-text("Selected Component")').first()).toBeVisible({ timeout: 6000 });
49+
const locator = replaceIframe.locator('div[id="forms-replace"] div span:has-text("Type:") + span');
50+
await expect(locator).toBeVisible({ timeout: 6000 });
51+
await expect(locator).not.toHaveText(/loading/i, { timeout: 6000 });
52+
const componentType = (await locator.textContent())?.trim();
53+
const replaceContext = { page, frame, replaceIframe };
54+
if (componentType === componentName1) {
55+
await replaceFormComponent(replaceContext, componentName2, partialUrl2);
56+
57+
} else if (componentType === componentName2) {
58+
await replaceFormComponent(replaceContext, componentName1, partialUrl1);
59+
60+
} else {
61+
throw new Error('Neither Email nor Password component was visible.');
62+
}
63+
});
64+
});
65+
66+
async function waitForAndValidateResponse(page, partialUrl) {
67+
const response = await page.waitForResponse(resp =>
68+
resp.url().includes(partialUrl) && resp.status() === 200
69+
);
70+
expect(response.url()).toContain(partialUrl);
71+
expect(response.status()).toBe(200);
72+
expect(response.ok()).toBe(true);
73+
}
74+
75+
async function replaceFormComponent(replaceContext, componentName, partialUrl) {
76+
const { page, frame, replaceIframe } = replaceContext;
77+
await replaceIframe.locator(selectors.replaceTextLocator + '+ button').click();
78+
await replaceIframe.locator(selectors.replaceTextLocator).type(componentName, { delay: 100 });
79+
const componentLocator = replaceIframe.getByText(componentName).last();
80+
await expect(componentLocator).toBeVisible();
81+
await componentLocator.waitFor({ state: 'visible' });
82+
await replaceIframe.getByText(componentName).last().click();
83+
await expect(replaceIframe.getByText(waringMessage)).toBeVisible();
84+
const replaceButton = replaceIframe.getByText('Replace').last();
85+
await replaceButton.page().waitForTimeout(300);
86+
await replaceButton.scrollIntoViewIfNeeded();
87+
await expect(replaceButton).toBeEnabled();
88+
await replaceButton.click();
89+
await waitForAndValidateResponse(page, partialUrl);
90+
const formReplaceButton = frame.locator(selectors.formReplace);
91+
await expect(formReplaceButton).toBeVisible();
92+
await formReplaceButton.click();
93+
await expect(
94+
replaceIframe
95+
.locator(`div[id="forms-replace"] div span:text-is("${componentName}")`)
96+
.first()
97+
).toBeVisible({timeout: 8000});
98+
}

0 commit comments

Comments
 (0)