diff --git a/.circleci/config.yml b/.circleci/config.yml index 78fb19dfeb..0f64e5d218 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,6 +3,7 @@ version: 2.1 orbs: codecov: codecov/codecov@1.0.5 browser-tools: circleci/browser-tools@1.4.3 + node: circleci/node@5.2.0 common: restore_cache: &restore_cache @@ -15,6 +16,8 @@ common: cypress_test_steps: &cypress_test_steps steps: - checkout + - node/install: + node-version: '22.19.0' - browser-tools/install-chrome - browser-tools/install-chromedriver - run: @@ -39,7 +42,7 @@ common: command: npm install chrome-launcher - run: name: Install lighthouse - command: npm install lighthouse + command: npm install lighthouse@13.5.0 - run: name: Install Web Driver command: npm install selenium-webdriver diff --git a/CLAUDE.md b/CLAUDE.md index 9d8ba79973..b9161e8eb5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,5 +13,7 @@ AEM Core Forms Components — Adaptive Forms v2 component library. When a change needs a new component version (`v1` → `v2`) vs. a fix-in-place, and the full checklist of what to update when bumping a version (clientlib self-containment, `_cq_dialog`/`_cq_styleConfig` inheritance, `pom.xml`, runtime clientlib embed lists, etc.). ### E2E Testing +- **SP/LTS Authoring Compatibility**: [`docs/e2e-testing/authoring-compatibility.md`](docs/e2e-testing/authoring-compatibility.md) + Stable selectors and Granite validation APIs, operation-specific waits, referrer requirements, normal-browser ngrok setup, and first-pass validation before repetition. - **Feature Toggle Tests**: [`docs/e2e-testing/feature-toggles.md`](docs/e2e-testing/feature-toggles.md) How to add Cypress e2e tests for new feature toggles: OSGi config changes, system property wiring, and the isLatestAddon + fetchFeatureToggles test pattern. diff --git a/docs/e2e-testing/authoring-compatibility.md b/docs/e2e-testing/authoring-compatibility.md new file mode 100644 index 0000000000..dfd269c9c7 --- /dev/null +++ b/docs/e2e-testing/authoring-compatibility.md @@ -0,0 +1,142 @@ +# Authoring test compatibility: 6.5 SP and LTS + +Authoring assertions must describe the field or editor behavior, not the markup +of one Coral implementation. A passing repetition on one server does not prove +compatibility with another AEM service pack or LTS instance. + +## Selectors and validation + +- Scope fields through our component classes and field names, for example + `.cmp-adaptiveform-checkboxgroup__value input[name='./default']`. +- Avoid internal presentation classes such as `.coral3-Search-input`, + `.coral-Form-errorlabel`, and `.coral-Form-fielderror`. Error UI can be a + tooltip, an icon, or an inline label. Covered or hidden error UI is not proof + that the field is valid. +- Use `cy.assertFieldInvalid(selector, message)` to require one field, + `aria-invalid="true"`, and the expected Granite validation message. It reads + the validation state after submission; it does not manufacture an error. +- The served AEM 6.5.25.0 `foundation-validation` adapter exposes + `getValidationMessage()`. The published Granite API also describes + `validationMessage()`. The shared command checks the available accessor + instead of branching on an AEM version string. A missing adapter, unsupported + accessor, ambiguous field, or unexpected message must fail explicitly. +- Use public Coral elements and values for selection through + `cy.selectCoralOption()`. Do not assume every `coral-select` has a native + `select[handle="nativeSelect"]`. +- Scope component insertion to the visible Insert dialog and require a unique + visible native input. Use the public dialog `open` property rather than + Coral's internal state classes when checking for a leftover dialog. + +The [Granite validation reference](https://developer.adobe.com/experience-manager/reference-materials/6-5/granite-ui/api/jcr_root/libs/granite/ui/components/coral/foundation/clientlibs/foundation/js/validation/index.html) +describes the adapter contract. Check the served clientlib when its API differs +from the reference; do not assume a documented method exists on every instance. + +## Layer selection and synchronization + +`cy.selectLayer()` waits for the requested layer to be visibly selected. +An overlay-reposition event is not an unconditional layer-selection contract. +Requiring it caused existing tests to time out on both SP and LTS even after +the selected-layer assertion succeeded. + +Keep operation-specific refresh waits for insertion, save, and deletion. +An event wait justified for one operation must not be imposed on every editor +transition. Test an actual layer change as well as an already-selected layer: +the latter returns early and cannot validate a transition wait. + +## Button inline editing + +An empty caption can remove both `jcr:title` and the rendered caption span. +Do not require a nonempty span after explicitly clearing the label. The inline +test saves a nonempty caption and verifies preview text and persisted title, +then repeats the original empty-label edit and verifies the empty native button +and absent/empty title before deletion. Both edits wait for the inline toolbar +to close. API teardown removes only the named test fixture if an assertion fails. + +Each RTE inline-editor case creates its own Button and enables and saves +`isTitleRichText` before opening the editor; it cannot reuse the preceding +case's fixture because `afterEach` deletes it. Opening retries the overlay/EDIT +action only while the inline toolbar is absent, within the existing ten-second +budget. Once the toolbar is visible, EDIT is not invoked again. A persistent +opening failure still fails the test rather than retrying the whole test. + +The Forms toolbar selects the plaintext editor when the rendered label/text +has no `data-richtext` attribute. A default Button therefore need not expose +`.rte-toolbar`: retrying EDIT or increasing the timeout cannot turn plaintext +editing into rich-text editing. RTE caption-edit tests must explicitly enable +rich text and wait for the configure save to refresh the editable. + +## Rule-save assertions + +Parse the rule-save POST's `:content` JSON and compare `fd:events.change` +structurally, as on master. Require the exact expected scripts rather than a +substring of serialized JSON. The equality-rule test cleans its named text-input +fixture before insertion and after each attempt so failed saves cannot leave +rules that accumulate on subsequent runs. It selects the edited field's generated +name explicitly as the hide target, rather than the first option: a leftover +sibling otherwise changes a self-hide rule into a `dispatchEvent` rule. + +## Authentication and browser user agents + +Use the normal browser user agent for acceptance. Sling's referrer filter +classifies browser requests using `Mozilla`/`Opera` in the user agent; changing +it to a non-browser value can hide a missing-referrer POST regression. + +Authenticated preferences, policy restoration, and fixture cleanup POSTs need +the session, CSRF token, and same-server referrer. Request-based login also +includes the configured `baseUrl` as its referrer. Await requests and preserve +HTTP failures; do not set `failOnStatusCode: false` to make rejected writes pass. + +## Remote ngrok runs with normal Chrome + +An ngrok browser warning can return HTTP 200 HTML instead of AEM JSON or +JavaScript (`ERR_NGROK_6024`). A missing `body.token` or JavaScript +`Unexpected token '<'` can therefore be a tunnel response, not an AEM selector +failure. Inspect the response content type before changing application code. + +For temporary remote support, forward +`ngrok-skip-browser-warning: true` on the initial `cy.visit()` and node-side +`cy.request()` calls. Browser subresources need the header too. The following +Chrome-only support hook was used successfully on the remote SP instance: + +```js +before(() => { + cy.then(() => Cypress.automation('remote:debugger:protocol', { + command: 'Network.enable' + }).then(() => Cypress.automation('remote:debugger:protocol', { + command: 'Network.setExtraHTTPHeaders', + params: {headers: {'ngrok-skip-browser-warning': 'true'}} + }))); +}); +``` + +This is tunnel-specific support, not a replacement for the normal CI browser +configuration. Broad middleware interception introduced while debugging stalled +the remote runner; removing it and setting the native browser header allowed the +same Text case to complete. Do not diagnose an AEM `visit()` problem solely from +a stalled custom runner. + +When inspecting Chrome, use the test browser's actual +`--remote-debugging-port`. The first `DevTools listening` line can belong to +Cypress's Electron process, not the test Chrome. An empty target list from the +wrong debugger does not show that the test browser has no page. + +## Acceptance order + +1. Verify the actual product version from + `/system/console/status-productinfo.txt` and the active component bundles. + A Core Components bundle version does not distinguish SP from LTS. +2. Capture fixture and policy baselines; preserve unrelated authored content. +3. Run every reported failing case once with global and per-test retries + disabled. Verify selected cases were collected and actually passed. +4. Include earlier reported cases when changing shared helpers. New-case passes + are not evidence that the original fixes still work. +5. Publish the verified fixes, then run the entire configured Cypress suite. + In `release/650`, the current inventory is 153 JavaScript specs. +6. Verify cleanup/restoration and obtain results for each CI variant. Record + pending, unsupported, skipped-after-hook, and retry-assisted results + separately from clean passes. + +A helper contract check against both accessor shapes is not a live LTS test. +Finite repetitions cannot guarantee that a race never recurs. Keep the PR +evidence explicit about the server, source revision, selected cases, and +remaining validation gaps. diff --git a/ui.tests/README.md b/ui.tests/README.md index abf547e156..0661d67753 100644 --- a/ui.tests/README.md +++ b/ui.tests/README.md @@ -28,6 +28,10 @@ npm run cypress:run:file -- ./specs/textinput/textinput.runtime.spec.js * Chrome and/or Firefox browser installed locally in default location * An AEM author instance running at http://localhost:4502 +CircleCI installs Node.js 22.19.0 before the shared UI-test dependencies and +uses Lighthouse 13.5.0. Lighthouse requires Node.js >=22.19; the older Node.js +bundled in the AEM test-runner images cannot parse its JSON import attributes. +Keep the CI Node.js and Lighthouse versions compatible when upgrading either. #### Remarks * After execution, reports and logs are available in `test-module/target/reports` folder diff --git a/ui.tests/test-module/README.md b/ui.tests/test-module/README.md index 3e79987a20..fb7a99b8ca 100644 --- a/ui.tests/test-module/README.md +++ b/ui.tests/test-module/README.md @@ -30,3 +30,53 @@ npm install ``` After execution, reports and logs are available in `target/reports` folder + +## Remote authoring and stability checks + +Override `baseUrl` rather than changing the committed localhost configuration: + +```sh +npx --no-install cypress run --browser chrome --headless \ + --spec 'specs/wizard/wizard.authoring.cy.js,specs/checkboxgroup/checkboxgroup.authoring.cy.js,specs/dropdown/dropdown.authoring.cy.js,specs/contentfragment/contentfragment.authoring.cy.js,specs/telephoneinput/telephoneinput.authoring.cy.js' \ + --config 'baseUrl=https://author.example,retries=0,video=false' \ + --env 'crx.loginViaRequest=true' +``` + +`crx.loginViaRequest` is optional and defaults to the existing UI login flow. +Enable it when an external author endpoint requires a session before navigation +or its UI login redirects escape the Cypress frame. It uses AEM's form-login +endpoint and the existing `crx.username`, `crx.password`, and `crx.contextPath` +settings. Supply credentials using Cypress environment configuration; do not +commit them. For an ngrok browser-warning endpoint, send its warning-bypass +header without replacing the normal browser user agent. See +[SP/LTS authoring compatibility](../../docs/e2e-testing/authoring-compatibility.md) +for the verified remote setup and cross-version assertion patterns. + +Component insertion must finish the editor refresh before a caller configures +or deletes the new component. +Use `cleanTest` after entering the Edit layer, `cancelConfigureDialog` for +configure-dialog cancellation, and `submitConfigureDialog` when a save must +finish before the next toolbar action. Select classic Coral options through +`selectCoralOption`, rather than assuming a native `select` or modern Coral +CSS classes. + +Use `createRule` to wait for the rule editor's statement builder, rather than +assuming its first Create click is handled while the iframe initializes. + +Layer selection waits for the selected-layer UI state, not an overlay-reposition +event that every layer transition may not emit. Use `assertFieldInvalid` to +check the field's invalid state and Granite validation message without depending +on Coral's version-specific tooltip or error-label markup. + +Use `cleanTestFixture` in teardown to remove only the named test-owned component +through authenticated requests, even if the editor or configure dialog failed. +Tutorial preferences are saved through awaited requests; request-login authoring +does not depend on the landing-page redirect. Authenticated preferences, policy, +and fixture-cleanup POSTs include both the CSRF token and the configured AEM +`baseUrl` as their referrer, including with the default browser user agent. + +Repeat stability runs with retries disabled, including per-test retry overrides. +Verify that test-owned components are removed and the telephone policy matches +its original snapshot after both passing and failing runs. +Use the default browser user agent for acceptance: a non-browser user agent can +bypass Sling's browser referrer checks and mask requests that fail in CI. diff --git a/ui.tests/test-module/libs/support/commands.js b/ui.tests/test-module/libs/support/commands.js index 44156f23e6..7e3061a4f4 100644 --- a/ui.tests/test-module/libs/support/commands.js +++ b/ui.tests/test-module/libs/support/commands.js @@ -62,6 +62,23 @@ Cypress.on('uncaught:exception', (err, runnable) => { Cypress.Commands.add("login", (pagePath, failurehandler = () => {}) => { const username = Cypress.env('crx.username') ? Cypress.env('crx.username') : "admin"; const password = Cypress.env('crx.password') ? Cypress.env('crx.password') : "admin"; + if (Cypress.env('crx.loginViaRequest') === true) { + const contextPath = Cypress.env('crx.contextPath') || ''; + return cy.request({ + method: 'POST', + url: contextPath + '/libs/granite/core/content/login.html/j_security_check', + form: true, + headers: {Referer: Cypress.config('baseUrl')}, + body: {j_username: username, j_password: password, j_validate: 'true', resource: contextPath + '/index.html'}, + followRedirect: false, + log: false + }).then(({status}) => { + expect(status, 'AEM form authentication').to.be.oneOf([200, 302]); + return cy.getCookie('login-token', {log: false}).then(cookie => { + expect(Boolean(cookie), 'AEM session established').to.equal(true); + }); + }); + } cy.get('#username').type(username); cy.get('#password').type(password); let retryCount = 0; @@ -96,6 +113,10 @@ function getUserInfoHome(contextPath) { cy.request(USER_INFO_SERVLET).its('body.home').as("home") } +function getAuthenticatedRequestHeaders(token) { + return {'CSRF-Token': token, Referer: Cypress.config('baseUrl')}; +} + // Cypress command to open authoring page Cypress.Commands.add("enableOrDisableTutorials", (enable) => { @@ -131,44 +152,66 @@ Cypress.Commands.add("enableOrDisableTutorials", (enable) => { }); cy.get("@home").then(home => { const url = contextPath + home + '/preferences'; - //cy.request('POST', url, preferences) // not using cy.request, since application level cookies needs to be passed - cy.window().then(win => { - win.$.post(url, preferences) + return cy.request({ + method: 'POST', + url, + form: true, + headers: getAuthenticatedRequestHeaders(preferences[":cq_csrf_token"]), + body: preferences, + log: false }); }); }); /** - * Resets a telephoneinput design policy to its baseline by removing the custom-format additions a test - * makes (`allowedCustomFormats` items and the `allowedFormat3` toggle). The design policy is shared, - * persistent repo state; without this, a test that adds a custom format leaves it behind and any later - * run/retry starts from a dirty policy — the multifield/Coral dialog then misbehaves and - * `allowedCustomFormats/item0/customFormatKey` never renders. Call in beforeEach for a deterministic start. + * Restores the mutable telephone policy fields from a snapshot, including existing custom formats. * @param policyPath JCR path of the telephoneinput policy node (…/policies/…/telephoneinput/) */ -Cypress.Commands.add("resetTelephoneInputDesignPolicy", (policyPath) => { +Cypress.Commands.add("restoreTelephoneInputDesignPolicy", (policyPath, policy) => { const contextPath = Cypress.env('crx.contextPath') ? Cypress.env('crx.contextPath') : ""; const url = contextPath + policyPath; - // POST via the app's jQuery inside the authenticated page (not cy.request) so the session cookie is - // carried — same reason enableOrDisableTutorials does; the sling `@Delete` removes the property/node. cy.request(contextPath + '/libs/granite/csrf/token.json').its('body.token').then((token) => { - cy.window().then((win) => new Cypress.Promise((resolve) => { - win.$.post(url, { + const options = {method: 'POST', url, form: true, headers: getAuthenticatedRequestHeaders(token), log: false}; + cy.request({...options, body: { 'allowedCustomFormats@Delete': '', 'allowedFormat3@Delete': '', ':cq_csrf_token': token - }).always(resolve); - })); + }}); + const body = {':cq_csrf_token': token}; + if (policy.allowedFormat3 !== undefined) { + body.allowedFormat3 = policy.allowedFormat3; + } + if (policy.allowedCustomFormats) { + body['allowedCustomFormats/jcr:primaryType'] = policy.allowedCustomFormats['jcr:primaryType']; + } + Object.entries(policy.allowedCustomFormats || {}).forEach(([item, values]) => { + if (typeof values === 'object' && values !== null) { + Object.entries(values).forEach(([name, value]) => { + if (name === 'jcr:primaryType' || !name.startsWith('jcr:')) { + body['allowedCustomFormats/' + item + '/' + name] = value; + } + }); + } + }); + cy.request({...options, body}); + }); + return cy.request(url + '.infinity.json').then(({body}) => { + expect(body.allowedFormat3, 'restored policy pattern').to.equal(policy.allowedFormat3); + expect(body.allowedCustomFormats, 'restored custom patterns').to.deep.equal(policy.allowedCustomFormats); }); }); // Cypress command to open AFv2 Cypress.Commands.add("openAFv2TemplateEditor", () => { const baseUrl = Cypress.env('crx.contextPath') ? Cypress.env('crx.contextPath') : ""; - cy.visit(baseUrl, {'failOnStatusCode': false}); - cy.login(baseUrl, () => { - cy.openAFv2TemplateEditor(); - }); + if (Cypress.env('crx.loginViaRequest') === true) { + cy.login(baseUrl); + } else { + cy.visit(baseUrl, {'failOnStatusCode': false}); + cy.login(baseUrl, () => { + cy.openAFv2TemplateEditor(); + }); + } cy.openTemplateEditor("/conf/core-components-examples/settings/wcm/templates/af-blank-v2/structure.html"); }); @@ -276,11 +319,15 @@ Cypress.Commands.add("openTemplateEditor", (templatePath) => { // Cypress command to open authoring page Cypress.Commands.add("openAuthoring", (pagePath) => { const baseUrl = Cypress.env('crx.contextPath') ? Cypress.env('crx.contextPath') : ""; - // getting status 403 intermittently, just ignore it - cy.visit(baseUrl, {'failOnStatusCode': false}); - cy.login(baseUrl, () => { - cy.openAuthoring(pagePath); - }); + if (Cypress.env('crx.loginViaRequest') === true) { + cy.login(baseUrl); + } else { + // getting status 403 intermittently, just ignore it + cy.visit(baseUrl, {'failOnStatusCode': false}); + cy.login(baseUrl, () => { + cy.openAuthoring(pagePath); + }); + } cy.openSiteAuthoring(pagePath); }); @@ -289,9 +336,12 @@ Cypress.Commands.add("openPage", (pagePath, options = {}) => { const contextPath = Cypress.env('crx.contextPath') ? Cypress.env('crx.contextPath') : ""; let path = ((contextPath && !pagePath.startsWith(contextPath)) ? `${contextPath}${pagePath.startsWith('/') ? '' : '/'}${pagePath}` : pagePath); if (!options.noLogin) { - // getting status 403 intermittently, just ignore it - const baseUrl = contextPath; - cy.visit(baseUrl, {'failOnStatusCode': false}); + if (Cypress.env('crx.loginViaRequest') === true) { + cy.login(contextPath); + } else { + // getting status 403 intermittently, just ignore it + const baseUrl = contextPath; + cy.visit(baseUrl, {'failOnStatusCode': false}); cy.getCookie('login-token').then(cookie => { if(!cookie) { cy.login(baseUrl, () => { @@ -299,6 +349,7 @@ Cypress.Commands.add("openPage", (pagePath, options = {}) => { }); } }) + } } const defaultOptions = { retryOnStatusCodeFailure: true, @@ -310,11 +361,27 @@ Cypress.Commands.add("openPage", (pagePath, options = {}) => { // cypress command to select layer in authoring Cypress.Commands.add("selectLayer", (layer) => { - // please note: when switching from style to other layer, we refresh guide, so these events need to be checked here - cy.get(siteSelectors.selectLayer.trigger).click(); - cy.get(siteSelectors.selectLayer.popover.self + ' [data-layer="' + layer + '"]').should('be.visible'); - cy.get(siteSelectors.selectLayer.popover.self + ' [data-layer="' + layer + '"]').click({force: true}); - cy.get(siteSelectors.selectLayer.current + '[data-layer="' + layer + '"].is-selected'); + const selectedLayer = siteSelectors.selectLayer.current + '[data-layer="' + layer + '"].is-selected'; + const layerOption = siteSelectors.selectLayer.popover.self + ' [data-layer="' + layer + '"]'; + return cy.get(siteSelectors.selectLayer.current).then($layers => { + if ($layers.filter('[data-layer="' + layer + '"].is-selected').length) { + return cy.get(selectedLayer).should('be.visible'); + } + recurse( + () => { + cy.get('body').then($body => { + if (!$body.find(siteSelectors.selectLayer.popover.self).is(':visible')) { + cy.get(siteSelectors.selectLayer.trigger).should('be.visible').click(); + } + }); + return cy.get('body'); + }, + $body => $body.find(layerOption).is(':visible'), + {limit: 21, delay: 500, timeout: 10000, log: false} + ); + cy.get(layerOption).click({force: true}); + return cy.get(selectedLayer).should('be.visible'); + }); }); // cypress command to open editable toolbar @@ -352,6 +419,72 @@ Cypress.Commands.add("invokeEditableAction", (actionSelector) => { cy.get(actionSelector).should('be.visible').click({force: true}); }); +Cypress.Commands.add("assertFieldInvalid", (selector, message) => { + return cy.window().then(win => { + return cy.get(selector).should($field => { + expect($field, 'unique validation field').to.have.length(1); + expect($field).to.have.attr('aria-invalid', 'true'); + const validation = win.$($field[0]).adaptTo('foundation-validation'); + expect(validation, 'Granite field validation API').to.exist; + const messageAccessor = typeof validation.getValidationMessage === 'function' + ? validation.getValidationMessage : validation.validationMessage; + expect(messageAccessor, 'Granite validation message accessor').to.be.a('function'); + expect(messageAccessor.call(validation), 'field validation message').to.equal(message); + }); + }); +}); + +Cypress.Commands.add("cancelConfigureDialog", () => { + return recurse( + () => { + cy.get('body').then($body => { + if ($body.find('.cq-dialog-cancel:visible').length) { + cy.get('.cq-dialog-cancel:visible').should('have.length', 1).click(); + } + }); + return cy.get('body'); + }, + $body => !$body.find('.cq-dialog-cancel:visible').length, + {limit: 5, delay: 500, timeout: 10000, log: false} + ); +}); + +Cypress.Commands.add("createRule", () => { + const statement = guideSelectors.ruleEditor.choiceModels.STATEMENT + ' .child-choice-name'; + return recurse( + () => { + cy.getRuleEditorIframe().should('not.have.class', 'af-freezeBody').then($body => { + if (!$body.find(statement).is(':visible')) { + cy.wrap($body).find(guideSelectors.ruleEditor.action.createRuleButton) + .should('be.visible').and('not.be.disabled').click(); + } + }); + return cy.getRuleEditorIframe(); + }, + $body => $body.find(statement).is(':visible'), + {limit: 5, delay: 500, timeout: 10000, log: false} + ); +}); + +Cypress.Commands.add("selectCoralOption", (selector, value) => { + cy.get(selector).should('have.length', 1).scrollIntoView().should('be.visible'); + cy.get(selector).find('button').first().should('be.visible').click(); + cy.get(selector).find('coral-selectlist-item[value="' + value + '"]') + .should('have.length', 1).and('be.visible').click(); + return cy.get(selector).should('have.value', value); +}); + +Cypress.Commands.add("fillTextField", (selector, value) => { + return recurse( + () => { + cy.get(selector).scrollIntoView().should('be.visible').and('not.be.disabled').clear(); + return cy.get(selector).type(value); + }, + $input => $input.val() === value, + {limit: 5, delay: 500, timeout: 10000, log: false} + ); +}); + // cypress command to submit a component's configure dialog and wait for the editor to settle. // A config-dialog submit fires an asynchronous editable re-render that repositions the overlays and // hides the shared #EditableToolbar. Any openEditableToolbar issued before that re-render settles @@ -363,6 +496,7 @@ Cypress.Commands.add("submitConfigureDialog", (submitSelector = ".cq-dialog-subm cy.initializeEventHandlerOnChannel(siteConstants.EVENT_NAME_EDITABLES_UPDATED).as("isConfigureEditableUpdated"); cy.initializeEventHandlerOnChannel(siteConstants.EVENT_NAME_OVERLAYS_REPOSITIONED).as("isConfigureOverlaysRepositioned"); cy.get(submitSelector).click({force: true}); + cy.get('.cq-dialog-submit:visible').should('not.exist'); cy.get("@isConfigureEditableUpdated").its('done').should('equal', true); // wait until re-render done cy.get("@isConfigureOverlaysRepositioned").its('done').should('equal', true); // wait until overlays settled }); @@ -585,14 +719,45 @@ Cypress.Commands.add("cleanTest", (editPath) => { // once it actually finished, so callers chaining off cleanTest could race ahead of the // real deletion. Returning the cypress command chain directly makes cleanTest actually // wait for deletion to complete. - return cy.get("body").then($body => { - const selector12 = "[data-path='" + editPath + "']"; - if ($body.find(selector12).length > 0) { + const contextPath = Cypress.env('crx.contextPath') || ''; + cy.selectLayer("Edit"); + return cy.request({url: contextPath + editPath + '.json', failOnStatusCode: false}).then(response => { + expect(response.status, 'fixture lookup').to.be.oneOf([200, 404]); + if (response.status === 200) { + cy.get(siteSelectors.overlays.overlay.component + "[data-path='" + editPath + "']").should('exist'); return cy.deleteComponentByPath(editPath); } }); }) +Cypress.Commands.add("cleanTestFixture", (componentPath) => { + const contextPath = Cypress.env('crx.contextPath') || ''; + const url = contextPath + componentPath; + return cy.request({url: url + '.json', failOnStatusCode: false, log: false}).then(response => { + expect(response.status, 'fixture cleanup lookup').to.be.oneOf([200, 404]); + if (response.status === 404) { + return; + } + expect(response.body['sling:resourceType'], 'test fixture resource type').to.be.a('string'); + expect(response.body['sling:resourceType'].split('/').pop(), 'owned fixture name') + .to.equal(componentPath.split('/').pop()); + cy.request({url: contextPath + '/libs/granite/csrf/token.json', log: false}).its('body.token').then(token => { + cy.request({ + method: 'POST', url, form: true, headers: getAuthenticatedRequestHeaders(token), + body: {':operation': 'delete', ':cq_csrf_token': token}, log: false + }); + }); + return recurse( + () => cy.request({url: url + '.json', failOnStatusCode: false, log: false}), + result => { + expect(result.status, 'removed fixture lookup').to.be.oneOf([200, 404]); + return result.status === 404; + }, + {limit: 10, delay: 500, timeout: 20000, log: false} + ); + }); +}); + Cypress.Commands.add("cleanTitleTest", (editPath) => { // clean the test before the next run, if any. Same fix as cleanTest above: chain the // deletions on the returned cypress command instead of resolving a wrapping Promise early. @@ -630,7 +795,8 @@ Cypress.Commands.add("deleteComponentByPath", (componentPath) => { // failure screenshot showing the edit dialog still open when the delete-confirm dialog was // expected). Wait for any leftover open dialog to actually close first. cy.get('body').should($body => { - expect($body.find('coral-dialog.is-open').length, 'no leftover open dialog before delete').to.equal(0); + const openDialogs = $body.find('coral-dialog').filter((index, dialog) => dialog.open); + expect(openDialogs.filter(':visible').length, 'no leftover open dialog before delete').to.equal(0); }); // open editable toolbar cy.openEditableToolbar(siteSelectors.overlays.overlay.component + componentPathSelector); @@ -644,6 +810,15 @@ Cypress.Commands.add("deleteComponentByPath", (componentPath) => { // wait for event to complete to signify deletion is complete cy.get("@isEditableUpdateEventComplete").its('done').should('equal', true); // wait here until done cy.get("@isOverlayRepositionEventComplete").its('done').should('equal', true); // wait here until done + const contextPath = Cypress.env('crx.contextPath') || ''; + return recurse( + () => cy.request({url: contextPath + componentPath + '.json', failOnStatusCode: false}), + response => { + expect(response.status, 'deleted fixture lookup').to.be.oneOf([200, 404]); + return response.status === 404; + }, + {limit: 10, delay: 500, timeout: 20000, log: false} + ); }); // cypress command to delete component by title @@ -657,7 +832,8 @@ Cypress.Commands.add("deleteComponentByTitle", (title) => { cy.initializeEventHandlerOnChannel(overlayRepositionEvent).as("isOverlayRepositionEventComplete"); // Same leftover-open-dialog race as deleteComponentByPath above; wait for it to actually close. cy.get('body').should($body => { - expect($body.find('coral-dialog.is-open').length, 'no leftover open dialog before delete').to.equal(0); + const openDialogs = $body.find('coral-dialog').filter((index, dialog) => dialog.open); + expect(openDialogs.filter(':visible').length, 'no leftover open dialog before delete').to.equal(0); }); // open editable toolbar cy.openEditableToolbar(siteSelectors.overlays.overlay.component + componentPathSelector); @@ -674,22 +850,50 @@ Cypress.Commands.add("deleteComponentByTitle", (title) => { // cypress command to insert component Cypress.Commands.add("insertComponent", (selector, componentString, componentType) => { - //Open toolbar of root panel - const insertComponentDialog_Selector = '.InsertComponentDialog-components [value="' + componentType + '"]', - insertComponentDialog_searchField = ".InsertComponentDialog-components .coral3-Search-input"; - cy.openEditableToolbar(selector); - cy.get(guideSelectors.editableToolbar.actions.insert).should('be.visible').click(); + const dialog = '.InsertComponentDialog:visible', + insertComponentDialog_Selector = dialog + ' [value="' + componentType + '"]', + insertComponentDialog_searchField = dialog + " input:not([type='hidden']):visible"; + recurse( + () => { + cy.get('body').then($body => { + if (!$body.find(dialog).length) { + cy.openEditableToolbar(selector); + cy.get(guideSelectors.editableToolbar.actions.insert).should('be.visible').click(); + } + }); + return cy.get('body'); + }, + $body => $body.find(dialog).length === 1, + {limit: 5, delay: 1000, timeout: 30000, log: false} + ); recurse( // the commands to repeat, and they yield the input element - () => cy.get(insertComponentDialog_searchField).clear().type(componentString), + () => { + cy.get(insertComponentDialog_searchField).should('have.length', 1).and('be.visible').clear(); + return cy.get(insertComponentDialog_searchField).type(componentString); + }, // the predicate takes the output of the above commands // and returns a boolean. If it returns true, the recursion stops ($input) => $input.val() === componentString, ) cy.get(insertComponentDialog_searchField).type('{enter}'); - cy.get(insertComponentDialog_Selector).should('be.visible');// basically should assertions does implicit retry in cypress + // Granite rebuilds the options asynchronously when the search is applied. + cy.get(dialog + ' coral-selectlist-item').should($items => { + const visibleItems = $items.filter(':visible'); + expect(visibleItems.length, 'filtered insert options').to.be.greaterThan(0); + visibleItems.each((index, item) => { + expect(item.textContent.trim().toLowerCase(), 'insert search applied') + .to.include(componentString.toLowerCase()); + }); + }); + cy.get(insertComponentDialog_Selector).should('have.length', 1).and('be.visible'); + cy.initializeEventHandlerOnChannel(siteConstants.EVENT_NAME_EDITABLES_UPDATED).as("isInsertEditableUpdated"); + cy.initializeEventHandlerOnChannel(siteConstants.EVENT_NAME_OVERLAYS_REPOSITIONED).as("isInsertOverlaysRepositioned"); // refer https://docs.cypress.io/guides/references/error-messages.html#cy-failed-because-the-element-you-are-chaining-off-of-has-become-detached-or-removed-from-the-dom cy.get(insertComponentDialog_Selector).click({force: true}); // sometimes AEM popover is visible, hence adding force here + cy.get(dialog).should('not.exist'); + cy.get("@isInsertEditableUpdated").its('done').should('equal', true); + return cy.get("@isInsertOverlaysRepositioned").its('done').should('equal', true); }); /** diff --git a/ui.tests/test-module/specs/button/button.authoring.cy.js b/ui.tests/test-module/specs/button/button.authoring.cy.js index 1388b5a7af..b886530166 100644 --- a/ui.tests/test-module/specs/button/button.authoring.cy.js +++ b/ui.tests/test-module/specs/button/button.authoring.cy.js @@ -17,6 +17,7 @@ const sitesSelectors = require('../../libs/commons/sitesSelectors'), afConstants = require('../../libs/commons/formsConstants'); +const {recurse} = require('cypress-recurse'); /** * Testing Form Button with Sites Editor @@ -73,20 +74,74 @@ describe('Button - Authoring', function () { cy.deleteComponentByPath(buttonDrop); } + const openButtonInlineEditor = function(buttonEditPathSelector) { + recurse( + () => { + cy.get('body').then($body => { + if (!$body.find('.rte-toolbar:visible').length) { + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + buttonEditPathSelector); + cy.get('body').then($currentBody => { + if (!$currentBody.find('.rte-toolbar:visible').length) { + cy.invokeEditableAction("[data-action='EDIT']"); + } + }); + } + }); + return cy.get('body'); + }, + $body => $body.find('.rte-toolbar:visible').length === 1, + {limit: 21, delay: 500, timeout: 10000, log: false} + ); + return cy.get('.rte-toolbar:visible').should('have.length', 1); + }; + + const enableButtonRichText = function(buttonEditPathSelector) { + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + buttonEditPathSelector); + cy.invokeEditableAction("[data-action='CONFIGURE']"); + cy.get("input[name='./isTitleRichText'][type='checkbox']").check({force: true}).should('be.checked'); + cy.get("div[name='richTextTitle']").should('be.visible'); + cy.submitConfigureDialog(); + }; + const testButtonBehaviourInilineEdit = function(buttonEditPathSelector, buttonDrop, isSites) { + const updatedLabel = 'Updated Button'; if (isSites) { dropButtonInSites(); } else { dropButtonInContainer(); } - cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + buttonEditPathSelector); - cy.invokeEditableAction("[data-action='EDIT']").then(() => { - getContentIframeBody().find('.cmp-adaptiveform-button__text').then(($span) => { - $span[0].textContent = ''; + enableButtonRichText(buttonEditPathSelector); + const contextPath = Cypress.env('crx.contextPath') || ''; + [updatedLabel, ''].forEach(label => { + openButtonInlineEditor(buttonEditPathSelector); + getContentIframeBody().find('.cmp-adaptiveform-button__text').should('have.length', 1) + .then(($span) => { + $span[0].textContent = label; + }); + recurse( + () => { + cy.get('body').then($body => { + if ($body.find('.rte-toolbar:visible').length) { + cy.get('body').click(0, 0); + } + }); + return cy.get('body'); + }, + $body => !$body.find('.rte-toolbar:visible').length, + {limit: 21, delay: 500, timeout: 10000, log: false} + ); + getContentIframeBody().find('.cmp-adaptiveform-button button').should($button => { + expect($button).to.have.length(1); + expect($button.text().trim()).to.equal(label); }); - cy.get('body').click(0,0); - cy.get("[data-path='/content/forms/af/core-components-it/blank/jcr:content/guideContainer/button']").should('exist').should('not.have.text', ''); - }) + cy.request(contextPath + buttonDrop + '.json').its('body').should(body => { + if (label) { + expect(body['jcr:title']).to.equal(label); + } else { + expect([undefined, '']).to.include(body['jcr:title']); + } + }); + }); }; @@ -100,6 +155,10 @@ describe('Button - Authoring', function () { cy.openAuthoring(pagePath); }); + afterEach(function () { + cy.cleanTestFixture(buttonDrop); + }); + it('insert Button in form container', function () { dropButtonInContainer(); cy.deleteComponentByPath(buttonDrop); @@ -111,7 +170,7 @@ describe('Button - Authoring', function () { }); }); - it ('open Inline edit dialog of Button',{ retries: 3 }, function(){ + it ('open Inline edit dialog of Button', function(){ cy.cleanTest(buttonDrop).then(function(){ testButtonBehaviourInilineEdit(buttonEditPathSelector, buttonDrop); cy.deleteComponentByPath(buttonDrop); @@ -131,11 +190,14 @@ describe('Button - Authoring', function () { }); it('check rich text inline editor is present', function(){ - cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + buttonEditPathSelector); - cy.invokeEditableAction("[data-action='EDIT']"); - cy.get(".rte-toolbar").should('be.visible'); - cy.get('.rte-toolbar-item[title="Close"]').should('be.visible').click(); - cy.deleteComponentByPath(buttonDrop); + cy.cleanTest(buttonDrop).then(function() { + dropButtonInContainer(); + enableButtonRichText(buttonEditPathSelector); + openButtonInlineEditor(buttonEditPathSelector); + cy.get('.rte-toolbar:visible .rte-toolbar-item[title="Close"]').should('be.visible').click(); + cy.get('.rte-toolbar:visible').should('not.exist'); + cy.deleteComponentByPath(buttonDrop); + }); }); }); diff --git a/ui.tests/test-module/specs/checkboxgroup/checkboxgroup.authoring.cy.js b/ui.tests/test-module/specs/checkboxgroup/checkboxgroup.authoring.cy.js index 75c2d0702f..dc3953a290 100644 --- a/ui.tests/test-module/specs/checkboxgroup/checkboxgroup.authoring.cy.js +++ b/ui.tests/test-module/specs/checkboxgroup/checkboxgroup.authoring.cy.js @@ -112,20 +112,42 @@ describe('Page - Authoring', function () { beforeEach(function () { // this is done since cypress session results in 403 sometimes cy.openAuthoring(pagePath); + cy.cleanTest(checkBoxGroupDrop); }); + afterEach(function () { + cy.cleanTestFixture(checkBoxGroupDrop); + }); + + const getGroup = () => getPreviewIframeBody().find('.cmp-adaptiveform-checkboxgroup').should('have.length', 1); + const configureRichTextOptions = () => { + dropCheckBoxGroupInContainer(); + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + checkBoxGroupEditPathSelector); + cy.invokeEditableAction("[data-action='CONFIGURE']"); + cy.get("input[name='./isTitleRichText'][type='checkbox']").check({force: true}).should('be.checked'); + cy.get("div[name='richTextTitle']").scrollIntoView().should('be.visible'); + cy.get("input[name='./areOptionsRichText'][type='checkbox']").check({force: true}).should('be.checked'); + cy.get("[data-cq-richtext-editable='true'][data-wrapperclass='cmp-adaptiveform-base__richTextEnumNames']") + .first().scrollIntoView().should('be.visible').focus().clear().type("Select 1").should('contain.text', "Select 1"); + cy.get("input[name='./orientation'][value='vertical']").scrollIntoView().click(); + cy.submitConfigureDialog(); + getGroup().should('contain.text', "Select 1"); + cy.openSiteAuthoring(pagePath); + getGroup().should('contain.text', "Select 1"); + }; + it('insert CheckBoxGroup in form container', function () { dropCheckBoxGroupInContainer(); cy.deleteComponentByPath(checkBoxGroupDrop); }); - it ('open edit dialog of CheckboxGroup', { retries: 3 }, function(){ + it ('open edit dialog of CheckboxGroup', function(){ cy.cleanTest(checkBoxGroupDrop).then(function() { testCheckBoxGroupBehaviour(checkBoxGroupEditPathSelector, checkBoxGroupDrop); }); }); - it ('check value type validations', { retries: 3 }, function() { + it ('check value type validations', function() { cy.cleanTest(checkBoxGroupDrop).then(function() { // For Number Type dropCheckBoxGroupInContainer(); @@ -137,10 +159,10 @@ describe('Page - Authoring', function () { cy.get('.cmp-adaptiveform-checkboxgroup__value button').click(); cy.get(".cmp-adaptiveform-checkboxgroup__value input").invoke('val', 'Not a Number'); cy.get('.cq-dialog-submit').click(); - cy.get('.coral3-Tooltip--error coral-tooltip-content').should('be.visible').should('have.text', 'Value Type Mismatch'); + cy.assertFieldInvalid(".cmp-adaptiveform-checkboxgroup__value input[name='./default']", 'Value Type Mismatch'); - cy.get('.cq-dialog-cancel').click(); + cy.cancelConfigureDialog(); cy.deleteComponentByPath(checkBoxGroupDrop); // For Boolean @@ -153,14 +175,14 @@ describe('Page - Authoring', function () { cy.get('.cmp-adaptiveform-checkboxgroup__value button').click(); cy.get(".cmp-adaptiveform-checkboxgroup__value input").invoke('val', 'Not a Boolean'); cy.get('.cq-dialog-submit').click(); - cy.get('.coral-Form-fielderror').should('be.visible').invoke('attr', 'aria-label').should('eq', 'Value Type Mismatch'); + cy.assertFieldInvalid(".cmp-adaptiveform-checkboxgroup__value input[name='./default']", 'Value Type Mismatch'); - cy.get('.cq-dialog-cancel').click(); + cy.cancelConfigureDialog(); cy.deleteComponentByPath(checkBoxGroupDrop); }); }) - it ('check for duplicate enum values', { retries: 3 }, function() { + it ('check for duplicate enum values', function() { cy.cleanTest(checkBoxGroupDrop).then(function() { dropCheckBoxGroupInContainer(); cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + checkBoxGroupEditPathSelector); @@ -168,18 +190,14 @@ describe('Page - Authoring', function () { cy.get("[data-granite-coral-multifield-name='./enum'] coral-button-label:contains('Add')").should("exist").click({force: true}); cy.get('input[name="./enum"]').last().invoke('val', '0'); cy.get('input[name="./enumNames"]').last().invoke('val', 'Item 3'); - cy.get('.cq-dialog-submit').click().then(() => { - cy.get('.cq-dialog-submit').should('not.exist') - }); - getPreviewIframeBody().find('.cmp-adaptiveform-checkboxgroup-item').should('have.length', 2); - getPreviewIframeBody().find('.cmp-adaptiveform-checkboxgroup').parent().parent().contains('Item 3'); - getPreviewIframeBody().find('.cmp-adaptiveform-checkboxgroup').parent().parent().contains('Item 2'); - getPreviewIframeBody().find('.cmp-adaptiveform-checkboxgroup').parent().parent().contains('Item 1').should('not.exist'); + cy.submitConfigureDialog(); + getGroup().find('.cmp-adaptiveform-checkboxgroup-item').should('have.length', 2); + getGroup().should('contain.text', 'Item 3').and('contain.text', 'Item 2').and('not.contain.text', 'Item 1'); cy.deleteComponentByPath(checkBoxGroupDrop); }); }); - it('check rich text support for label', {retries: 2}, function(){ + it('check rich text support for label', function(){ cy.cleanTest(checkBoxGroupDrop).then(function() { dropCheckBoxGroupInContainer(); cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + checkBoxGroupEditPathSelector); @@ -187,13 +205,12 @@ describe('Page - Authoring', function () { cy.get("div[name='richTextTitle']").should('not.be.visible'); // check rich text selector and see if RTE is visible for title. - cy.get('.cmp-adaptiveform-base__istitlerichtext').should('be.visible').click(); - cy.wait(500); // Add small wait for UI update + cy.get("input[name='./isTitleRichText'][type='checkbox']").check({force: true}).should('be.checked'); cy.get("div[name='richTextTitle']").should('exist').scrollIntoView().should('be.visible'); // check rich text selector and see if RTE is visible for enum names. cy.get(".cmp-adaptiveform-base__richTextEnumNames").first().should('not.be.visible'); - cy.get('.cmp-adaptiveform-base__areOptionsRichText').should('exist').click(); + cy.get("input[name='./areOptionsRichText'][type='checkbox']").check({force: true}).should('be.checked'); cy.get("div[name='richTextEnumNames']").then(($el) => { $el[0].scrollIntoView(); }) @@ -201,33 +218,27 @@ describe('Page - Authoring', function () { cy.get("div[name='richTextEnumNames']").first().should('be.visible'); cy.get(".cmp-adaptiveform-base__richTextEnumNames").first().should('be.visible'); cy.get("input[name='./orientation'][value='vertical']").scrollIntoView().click(); - cy.get('.cq-dialog-submit').click({ force: true }); - getPreviewIframeBody().find('.cmp-adaptiveform-checkboxgroup-item').should('have.length',2); - getPreviewIframeBody().find('.cmp-adaptiveform-checkboxgroup').parent().parent().contains('Select 1'); - getPreviewIframeBody().find('.cmp-adaptiveform-checkboxgroup').parent().parent().contains('Item 2'); + cy.submitConfigureDialog(); + getGroup().find('.cmp-adaptiveform-checkboxgroup-item').should('have.length',2); + getGroup().should('contain.text', 'Select 1').and('contain.text', 'Item 2'); }); }); - it('check rich text inline editor is present', {retries: 2}, function(){ + it('check rich text inline editor is present', function(){ + configureRichTextOptions(); cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + checkBoxGroupEditPathSelector); cy.invokeEditableAction("[data-action='EDIT']"); cy.get(".rte-toolbar").should('be.visible'); cy.get('.rte-toolbar-item[title="Close"]').should('be.visible').click(); }); - // adding retry since rule editor sometimes does not open at first try - it('rule editor is working with rich text enum names', {retries: 2}, function () { + it('rule editor is working with rich text enum names', function () { + configureRichTextOptions(); cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + checkBoxGroupEditPathSelector); cy.get(formsSelectors.ruleEditor.action.editRule).should("exist"); - cy.initializeEventHandlerOnChannel("af-rule-editor-initialized").as("isRuleEditorInitialized"); - cy.wait(1000); cy.get(formsSelectors.ruleEditor.action.editRule).click(); - // click on create option from rule editor header - // cy.get("@isRuleEditorInitialized").its('done').should('equal', true); - cy.wait(1000); - cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.createRuleButton).should("be.visible").click(); - cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.sideToggleButton + ":first").click(); + cy.createRule(); cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.STATEMENT + " .child-choice-name").should("exist"); cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.choiceModels.STATEMENT + " .child-choice-name").click(); diff --git a/ui.tests/test-module/specs/contentfragment/contentfragment.authoring.cy.js b/ui.tests/test-module/specs/contentfragment/contentfragment.authoring.cy.js index 11d875e5e4..96a7de93ae 100644 --- a/ui.tests/test-module/specs/contentfragment/contentfragment.authoring.cy.js +++ b/ui.tests/test-module/specs/contentfragment/contentfragment.authoring.cy.js @@ -65,13 +65,9 @@ describe('Content Fragment - Authoring', function () { .should("exist"); cy.get("[name='./fragmentPath'] input[role='combobox']").should("exist").clear() - .type("/content/dam/wknd/library/sample-assets/adobe-headquarters", { delay: 0 }).click().then(x => { - cy.get('.cq-dialog-submit').click().then(y => { - cy.get('.cq-dialog-submit').should('not.exist').then(z => { - cy.deleteComponentByPath(cfDrop); - }); - }); - }); + .type("/content/dam/wknd/library/sample-assets/adobe-headquarters", { delay: 0 }).click(); + cy.submitConfigureDialog(); + cy.deleteComponentByPath(cfDrop); } const dropExperienceFragmentInSites = function() { @@ -93,12 +89,8 @@ describe('Content Fragment - Authoring', function () { cy.get("[name='./fragmentVariationPath'] input[role='combobox']").should("exist").clear() .type("/content/experience-fragments/test-experience-fragment/master", { delay: 0 }).click(); cy.get(".foundation-picker-buttonlist button").should("be.visible").click(); - cy.get('.cq-dialog-submit').click().then(y => { - cy.get('.cq-dialog-submit').click(); - cy.get('.cq-dialog-submit').should('not.exist').then(z => { - cy.deleteComponentByPath(xfDrop); - }); - }); + cy.submitConfigureDialog(); + cy.deleteComponentByPath(xfDrop); } context("Open Forms Editor", function () { @@ -110,9 +102,14 @@ describe('Content Fragment - Authoring', function () { beforeEach(function () { // this is done since cypress session results in 403 sometimes cy.openAuthoring(pagePath); + cy.cleanTest(cfDrop); }); - it('insert content fragment inside adaptive form container component', { retries: 3 }, function() { + afterEach(function () { + cy.cleanTestFixture(cfDrop); + }); + + it('insert content fragment inside adaptive form container component', function() { cy.cleanTest(cfDrop).then(function() { testContentFragmentBehaviour(cfEditPathSelector, cfDrop, false); }); @@ -123,9 +120,18 @@ describe('Content Fragment - Authoring', function () { context("Open Sites Editor", function () { // we can use these values to log in const pagePath = "/content/core-components-examples/library/adaptive-form/container"; + const cfDrop = pagePath + afConstants.RESPONSIVE_GRID_DEMO_SUFFIX + '/formContainer/contentfragment'; + const xfDrop = pagePath + '/jcr:content/root/responsivegrid/experiencefragment'; beforeEach(function () { // this is done since cypress session results in 403 sometimes cy.openAuthoring(pagePath); + cy.cleanTest(cfDrop); + cy.cleanTest(xfDrop); + }); + + afterEach(function () { + cy.cleanTestFixture(cfDrop); + cy.cleanTestFixture(xfDrop); }); it('insert content fragment inside adaptive form container component', function() { diff --git a/ui.tests/test-module/specs/dropdown/dropdown.authoring.cy.js b/ui.tests/test-module/specs/dropdown/dropdown.authoring.cy.js index 73a35febf4..085c9744c7 100644 --- a/ui.tests/test-module/specs/dropdown/dropdown.authoring.cy.js +++ b/ui.tests/test-module/specs/dropdown/dropdown.authoring.cy.js @@ -66,7 +66,7 @@ describe('Page - Authoring', function () { cy.get('[name="./multiSelect"][type="checkbox"]').should("exist").uncheck(); cy.get(".cmp-adaptiveform-dropdown__defaultvalue").should("have.css","display","block"); - cy.get('.cq-dialog-cancel').click(); + cy.cancelConfigureDialog(); cy.deleteComponentByPath(dropdown); } @@ -87,6 +87,11 @@ describe('Page - Authoring', function () { beforeEach(function () { // this is done since cypress session results in 403 sometimes cy.openAuthoring(pagePath); + cy.cleanTest(dropdown); + }); + + afterEach(function () { + cy.cleanTestFixture(dropdown); }); it('insert Dropdown in form container', function () { @@ -108,9 +113,9 @@ describe('Page - Authoring', function () { cy.get("coral-selectlist-item").contains('Number').should('be.visible').click({force: true}); cy.get(".cmp-adaptiveform-dropdown__defaultvalue input").invoke('val', 'Not a Number'); cy.get('.cq-dialog-submit').click(); - cy.get('.coral-Form-fielderror').should('be.visible').invoke('attr', 'aria-label').should('eq', 'Value Type Mismatch'); + cy.assertFieldInvalid(".cmp-adaptiveform-dropdown__defaultvalue input[name='./default']", 'Value Type Mismatch'); - cy.get('.cq-dialog-cancel').click(); + cy.cancelConfigureDialog(); cy.deleteComponentByPath(dropdown); // For Boolean @@ -121,9 +126,9 @@ describe('Page - Authoring', function () { cy.get("coral-selectlist-item").contains('Boolean').click({force: true}); cy.get(".cmp-adaptiveform-dropdown__defaultvalue input").invoke('val', 'Not a Boolean'); cy.get('.cq-dialog-submit').click(); - cy.get('.coral-Form-fielderror').should('be.visible').invoke('attr', 'aria-label').should('eq', 'Value Type Mismatch'); + cy.assertFieldInvalid(".cmp-adaptiveform-dropdown__defaultvalue input[name='./default']", 'Value Type Mismatch'); - cy.get('.cq-dialog-cancel').click(); + cy.cancelConfigureDialog(); cy.deleteComponentByPath(dropdown); }) @@ -160,6 +165,11 @@ describe('Page - Authoring', function () { beforeEach(function () { // this is done since cypress session results in 403 sometimes cy.openAuthoring(pagePath); + cy.cleanTest(dropdown); + }); + + afterEach(function () { + cy.cleanTestFixture(dropdown); }); it('insert aem forms DropDown', function () { diff --git a/ui.tests/test-module/specs/ruleeditor/authoring/ruleEditor.authoring.cy.js b/ui.tests/test-module/specs/ruleeditor/authoring/ruleEditor.authoring.cy.js index 07452db07d..55cd069322 100644 --- a/ui.tests/test-module/specs/ruleeditor/authoring/ruleEditor.authoring.cy.js +++ b/ui.tests/test-module/specs/ruleeditor/authoring/ruleEditor.authoring.cy.js @@ -78,17 +78,11 @@ describe('Rule editor authoring sanity for core-components',function(){ cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.closeRuleEditor).click(); } - const createRuleToHideTextInputOnEqualityOperator = function() { + const createRuleToHideTextInputOnEqualityOperator = function(fieldName) { // Edit rule option not existing on button toolbar cy.get(formsSelectors.ruleEditor.action.editRule).should("exist"); - cy.initializeEventHandlerOnChannel("af-rule-editor-initialized").as("isRuleEditorInitialized"); - cy.wait(1000); cy.get(formsSelectors.ruleEditor.action.editRule).click(); - - // click on create option from rule editor header - // cy.get("@isRuleEditorInitialized").its('done').should('equal', true); - cy.wait(1000); - cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.createRuleButton).should("be.visible").click(); + cy.createRule(); cy.getRuleEditorIframe().find(formsSelectors.ruleEditor.action.sideToggleButton + ":first").click(); @@ -122,7 +116,9 @@ describe('Rule editor authoring sanity for core-components',function(){ cy.getRuleEditorIframe().find(".terminal-view.AFCOMPONENT.VARIABLE").should("be.visible"); cy.getRuleEditorIframe().find(".terminal-view.AFCOMPONENT.VARIABLE").click(); - cy.getRuleEditorIframe().find(".terminal-view.AFCOMPONENT.VARIABLE coral-overlay.is-open .expression-selectlist coral-selectlist-item:first").click({force: true}); + cy.getRuleEditorIframe().find(".terminal-view.AFCOMPONENT.VARIABLE coral-overlay.is-open .expression-selectlist coral-selectlist-item") + .filter((index, item) => item.value === "$form." + fieldName) + .should("have.length", 1).click({force: true}); cy.intercept('POST', /content\/forms\/af\/core-components-it\/samples\/ruleeditor\/blank.*/).as('ruleEditorRequest'); @@ -132,7 +128,10 @@ describe('Rule editor authoring sanity for core-components',function(){ cy.wait('@ruleEditorRequest').then((interception) => { expect(interception.response.statusCode).to.equal(201); const submittedData = Object.fromEntries(new URLSearchParams(interception.request.body)); - expect(submittedData[":content"]).contains("\"fd:events\":{\"change\":[\"if(contains($event.payload.changes[].propertyName, 'value'), if($field.$value == 'abc', {visible : false()}, {}), {})\"]}"); + const submittedContent = JSON.parse(submittedData[":content"]); + expect(submittedContent["fd:events"].change).to.deep.equal([ + "if(contains($event.payload.changes[].propertyName, 'value'), if($field.$value == 'abc', {visible : false()}, {}), {})" + ]); }); // check and close rule editor @@ -349,27 +348,37 @@ describe('Rule editor authoring sanity for core-components',function(){ cy.deleteComponentByPath(buttonEditPath); }) - it('should add rule on texbox equality operator to hide a text box', function () { - cy.openAuthoring(formPath); - cy.selectLayer("Edit"); - cy.get(sitesSelectors.overlays.overlay.component + "[data-path='" + formContainerPath + "/*']").should("exist"); + context('Text box equality rule', function () { + afterEach(function () { + cy.cleanTestFixture(textinputEditPath); + }); - cy.insertComponent(sitesSelectors.overlays.overlay.component + "[data-path='" + formContainerPath + "/*']", - "Adaptive Form Text Box", afConstants.components.forms.resourceType.formtextinput); - // cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + textinputEditPathSelector); - // cy.get(formsSelectors.ruleEditor.action.configure).should("exist"); - // cy.get(formsSelectors.ruleEditor.action.configure).click(); - // cy.get(".cmp-adaptiveform-base__editdialogbasic [name='./name']").clear().type("textinput"); - // cy.get(".cq-dialog-actions.cq-dialog-submit").click(); - - cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + textinputEditPathSelector); + it('should add rule on texbox equality operator to hide a text box', function () { + cy.openAuthoring(formPath); + cy.selectLayer("Edit"); + cy.get(sitesSelectors.overlays.overlay.component + "[data-path='" + formContainerPath + "/*']").should("exist"); + cy.cleanTest(textinputEditPath); - createRuleToHideTextInputOnEqualityOperator(); - cy.get(sitesSelectors.overlays.overlay.component + textinputEditPathSelector).should("exist"); + cy.insertComponent(sitesSelectors.overlays.overlay.component + "[data-path='" + formContainerPath + "/*']", + "Adaptive Form Text Box", afConstants.components.forms.resourceType.formtextinput); + // cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + textinputEditPathSelector); + // cy.get(formsSelectors.ruleEditor.action.configure).should("exist"); + // cy.get(formsSelectors.ruleEditor.action.configure).click(); + // cy.get(".cmp-adaptiveform-base__editdialogbasic [name='./name']").clear().type("textinput"); + // cy.get(".cq-dialog-actions.cq-dialog-submit").click(); + + const contextPath = Cypress.env('crx.contextPath') || ''; + cy.request(contextPath + textinputEditPath + '.json').its('body.name') + .should('be.a', 'string').and('not.be.empty').then(fieldName => { + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + textinputEditPathSelector); + createRuleToHideTextInputOnEqualityOperator(fieldName); + }); + cy.get(sitesSelectors.overlays.overlay.component + textinputEditPathSelector).should("exist"); - cy.selectLayer("Edit"); - cy.deleteComponentByPath(textinputEditPath); - }) + cy.selectLayer("Edit"); + cy.deleteComponentByPath(textinputEditPath); + }); + }); if (cy.af.isLatestAddon()) { it('should add validation rule on date fields', function () { diff --git a/ui.tests/test-module/specs/telephoneinput/telephoneinput.authoring.cy.js b/ui.tests/test-module/specs/telephoneinput/telephoneinput.authoring.cy.js index 692056aaa4..6010d9db79 100644 --- a/ui.tests/test-module/specs/telephoneinput/telephoneinput.authoring.cy.js +++ b/ui.tests/test-module/specs/telephoneinput/telephoneinput.authoring.cy.js @@ -52,7 +52,7 @@ describe('Page - Authoring', function () { cy.get("[name='./autocomplete']") .should("exist"); cy.get(bemEditDialog).contains('Validation').click({force:true}); - cy.get('.cq-dialog-cancel').should('be.visible').click({force: true}); + cy.cancelConfigureDialog(); cy.deleteComponentByPath(telephoneInputDrop); } @@ -64,6 +64,11 @@ describe('Page - Authoring', function () { beforeEach(function () { // this is done since cypress session results in 403 sometimes cy.openAuthoring(pagePath); + cy.cleanTest(telephoneInputDrop); + }); + + afterEach(function () { + cy.cleanTestFixture(telephoneInputDrop); }); it('insert TelephoneInput in form container', function () { @@ -71,7 +76,7 @@ describe('Page - Authoring', function () { cy.deleteComponentByPath(telephoneInputDrop); }); - it ('open edit dialog of TelephoneInput', { retries: 3 }, function(){ + it ('open edit dialog of TelephoneInput', function(){ cy.cleanTest(telephoneInputDrop).then(function() { testTelephoneInputBehaviour(telephoneInputEditPathSelector, telephoneInputDrop); }); @@ -84,9 +89,9 @@ describe('Page - Authoring', function () { cy.invokeEditableAction("[data-action='CONFIGURE']"); cy.get(bemEditDialog).contains('Validation').click({force: true}).then(() => { cy.get('.cmp-adaptiveform-telephoneinput__validationformat').should('have.value', '^[+][0-9]{0,14}$'); - cy.get('.cmp-adaptiveform-telephoneinput__validationpattern select').select('US Phone Number', {force: true}); + cy.selectCoralOption('.cmp-adaptiveform-telephoneinput__validationpattern', '^[+]1[0-9]{0,10}$'); cy.get('.cmp-adaptiveform-telephoneinput__validationformat').should('have.value', '^[+]1[0-9]{0,10}$'); - cy.get('.cq-dialog-cancel').click(); + cy.cancelConfigureDialog(); cy.deleteComponentByPath(telephoneInputDrop); }); }); @@ -104,36 +109,57 @@ describe('Page - Authoring', function () { telephoneInputEditPathSelector = "[data-path='" + telephoneInputEditPath + "']"; const customKey = 'customKey', customValue = 'customValue', - // Shared, persistent design policy this test mutates — reset it each run so a leftover custom - // format from a prior run/retry can't leave the dialog in a dirty state (see reset command). + // Snapshot this shared policy before editing and restore its original formats after each test. telephoneInputPolicyPath = '/conf/core-components-examples/settings/wcm/policies/forms-components-examples/components/form/telephoneinput/default'; + let originalPolicy; beforeEach(function () { - cy.openAuthoring(templateDataPath + ".html"); - cy.resetTelephoneInputDesignPolicy(telephoneInputPolicyPath); + originalPolicy = undefined; + cy.openAuthoring(templateDataPath); + const contextPath = Cypress.env('crx.contextPath') || ''; + cy.request(contextPath + telephoneInputPolicyPath + '.infinity.json').then(({body}) => { + originalPolicy = body; + cy.restoreTelephoneInputDesignPolicy(telephoneInputPolicyPath, {allowedFormat3: body.allowedFormat3}); + }); + }); + + afterEach(function () { + if (originalPolicy) { + cy.restoreTelephoneInputDesignPolicy(telephoneInputPolicyPath, originalPolicy); + } + cy.cleanTestFixture(telephoneInputDrop); }); it('Adding removing patterns from design policy', function () { cy.get(telephoneInputPolicy).click({force: true}); cy.get(bemDesignDialog).contains('Validation patterns').click(); // cy.get('[role="tablist"][orientation="horizontal"] [role="tab"]').eq(2).click(); - cy.get('[name="./allowedFormat3"]').eq(0).click({force: true}); + cy.get('input[name="./allowedFormat3"][type="checkbox"]').uncheck({force: true}).should('not.be.checked'); cy.get("[data-granite-coral-multifield-name='./allowedCustomFormats']").should('be.visible'); cy.get("[data-granite-coral-multifield-name='./allowedCustomFormats'] coral-button-label:contains('Add')").should('exist').click({force: true}); cy.get('[name="./allowedCustomFormats/item0/customFormatKey"]').should('exist').then(() => { - cy.get('[name="./allowedCustomFormats/item0/customFormatKey"]').focus().clear().type(customKey); - cy.get('[name="./allowedCustomFormats/item0/customFormatValue"]').focus().clear().type(customValue); - cy.get(submitBtnSelector).click(); + cy.fillTextField('[name="./allowedCustomFormats/item0/customFormatKey"]', customKey); + cy.fillTextField('[name="./allowedCustomFormats/item0/customFormatValue"]', customValue); + cy.intercept('POST', '**' + telephoneInputPolicyPath + '*', request => { + request.on('response', response => response.setDelay(1000)); + }).as('saveTelephonePolicy'); + cy.get(submitBtnSelector + ':visible').click(); + cy.wait('@saveTelephonePolicy').its('response.statusCode').should('be.oneOf', [200, 201]); + cy.get(bemDesignDialog + ':visible').should('not.exist'); + const contextPath = Cypress.env('crx.contextPath') || ''; + cy.request(contextPath + telephoneInputPolicyPath + '.infinity.json').its('body.allowedCustomFormats.item0') + .should('include', {customFormatKey: customKey, customFormatValue: customValue}); }).then(() => { cy.openSiteAuthoring(authoringPagePath); + cy.cleanTest(telephoneInputDrop); dropTelephoneInputInContainer(); cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + telephoneInputEditPathSelector); cy.invokeEditableAction("[data-action='CONFIGURE']"); cy.get(bemEditDialog).contains('Validation').click({force: true}).then(() => { cy.get('.cmp-adaptiveform-telephoneinput__validationformat').should('have.value', '^[+][0-9]{0,14}$'); - cy.get('.cmp-adaptiveform-telephoneinput__validationpattern select').select(customKey, {force: true}).then(() => { + cy.selectCoralOption('.cmp-adaptiveform-telephoneinput__validationpattern', customValue).then(() => { cy.get('.cmp-adaptiveform-telephoneinput__validationformat').should('have.value', customValue); - cy.get('.cq-dialog-cancel').click(); + cy.cancelConfigureDialog(); cy.deleteComponentByPath(telephoneInputDrop); }) }); @@ -150,6 +176,11 @@ describe('Page - Authoring', function () { beforeEach(function () { // this is done since cypress session results in 403 sometimes cy.openAuthoring(pagePath); + cy.cleanTest(telephoneInputDrop); + }); + + afterEach(function () { + cy.cleanTestFixture(telephoneInputDrop); }); it('insert aem forms TelephoneInput', function () { @@ -157,7 +188,7 @@ describe('Page - Authoring', function () { cy.deleteComponentByPath(telephoneInputDrop); }); - it('open edit dialog of aem forms TelephoneInput', { retries: 3 }, function() { + it('open edit dialog of aem forms TelephoneInput', function() { cy.cleanTest(telephoneInputDrop).then(function(){ testTelephoneInputBehaviour(telephoneInputEditPathSelector, telephoneInputDrop, true); }); diff --git a/ui.tests/test-module/specs/textinput/textinput.authoring.cy.js b/ui.tests/test-module/specs/textinput/textinput.authoring.cy.js index b0b440164f..c255ce53aa 100644 --- a/ui.tests/test-module/specs/textinput/textinput.authoring.cy.js +++ b/ui.tests/test-module/specs/textinput/textinput.authoring.cy.js @@ -214,6 +214,7 @@ describe('Page - Authoring', function () { it('should switch validation pattern dropdown to "Custom" when an unmapped regex is authored', function () { const customValidationFormatValue = '^custom-regex-[0-9]{3}$'; + cy.cleanTest(textInputDrop); dropTextInputInContainer(); cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + textInputEditPathSelector); cy.invokeEditableAction("[data-action='CONFIGURE']"); @@ -230,17 +231,16 @@ describe('Page - Authoring', function () { // Pick a non-default option (not empty, not "custom") so the format field is shown and synced. let chosenValue; - cy.get('@validationDropdown').find('select[handle="nativeSelect"]').then(($nativeSelect) => { - const options = Array.from($nativeSelect[0]?.options || []); + cy.get('@validationDropdown').find('coral-select-item[value]').then(($options) => { + const options = Array.from($options); const chosen = options.find((opt) => - Boolean(opt.value) && - opt.value !== 'custom' && - opt.value !== '#####################.###############' + Boolean(opt.getAttribute('value')) && + opt.getAttribute('value') !== 'custom' && + opt.getAttribute('value') !== '#####################.###############' ); expect(chosen, 'non-default validation pattern option').to.exist; - chosenValue = chosen.value; - cy.wrap($nativeSelect).select(chosenValue, { force: true }); - cy.wrap($nativeSelect).trigger('change', { force: true }); + chosenValue = chosen.getAttribute('value'); + cy.selectCoralOption('.cmp-adaptiveform-textinput__validationpattern', chosenValue); }); cy.then(() => { cy.get('@validationDropdown').should('have.value', chosenValue); @@ -260,7 +260,7 @@ describe('Page - Authoring', function () { cy.get('.cq-dialog').click(5, 5, { force: true }); cy.get('@validationDropdown').should('have.value', 'custom'); cy.get('@validationDropdown').children('button').should('contain.text', 'Custom'); - cy.get('.cq-dialog-cancel').should('be.visible').click(); + cy.cancelConfigureDialog(); cy.deleteComponentByPath(textInputDrop); }); }) diff --git a/ui.tests/test-module/specs/wizard/wizard.authoring.cy.js b/ui.tests/test-module/specs/wizard/wizard.authoring.cy.js index 339fbd6a91..7339cb326f 100644 --- a/ui.tests/test-module/specs/wizard/wizard.authoring.cy.js +++ b/ui.tests/test-module/specs/wizard/wizard.authoring.cy.js @@ -134,6 +134,10 @@ describe('Page - Authoring', function () { cy.openAuthoring(pagePath); }); + afterEach(function () { + cy.cleanTestFixture(wizardLayoutDrop); + }); + it('runtime library should not be loaded', function() { cy.cleanTest(wizardLayoutDrop).then(function () { cy.intercept('GET', /jcr:content\/guideContainer\/wizard\.html/).as('wizardRequest'); @@ -164,7 +168,7 @@ describe('Page - Authoring', function () { cy.get("[name='./dataRef']").should("exist"); cy.get("[name='./visible']").should("exist"); cy.get("[name='./enabled']").should("exist"); - cy.get('.cq-dialog-cancel').should('be.visible').click({force: true}).then(() => { + cy.cancelConfigureDialog().then(() => { cy.deleteComponentByPath(wizardLayoutDrop); }); }); @@ -174,14 +178,14 @@ describe('Page - Authoring', function () { }); }); - it('verify Navigation Working between tabs in Authoring', {retries: 3}, function () { + it('verify Navigation Working between tabs in Authoring', function () { cy.cleanTest(wizardLayoutDrop).then(function () { dropWizardInContainer(); addComponentInWizard("Adaptive Form Number Input", afConstants.components.forms.resourceType.formnumberinput); addComponentInWizard("Adaptive Form Text Box", afConstants.components.forms.resourceType.formtextinput); cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + wizardEditPathSelector); cy.invokeEditableAction(editDialogNavigationPanelSelector); - cy.wait(2000).then(() => { + cy.then(() => { cy.get("table.cmp-panelselector__table").find("tr").should("have.length", 2); // In select panel, text will be in format: : cy.get("table.cmp-panelselector__table tr").eq(0) @@ -200,14 +204,14 @@ describe('Page - Authoring', function () { }); }); - it('open editable toolbar of 2nd wizard panel', {retries: 3}, function () { + it('open editable toolbar of 2nd wizard panel', function () { cy.cleanTest(wizardLayoutDrop).then(function () { dropWizardInContainer(); addComponentInWizard("Adaptive Form Number Input", afConstants.components.forms.resourceType.formnumberinput); addComponentInWizard("Adaptive Form Panel", afConstants.components.forms.resourceType.panelcontainer); cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + wizardEditPathSelector); cy.invokeEditableAction(editDialogNavigationPanelSelector); - cy.wait(2000).then(() => { + cy.then(() => { cy.get("table.cmp-panelselector__table").find("tr").should("have.length", 2); cy.get("table.cmp-panelselector__table").find(panelcontainerDataId).find("td").first().should('be.visible').click(); cy.get('body').click(0, 0); @@ -260,15 +264,18 @@ describe('Page - Authoring', function () { cy.openAuthoring(pagePath); }); - it('insert aem forms Wizard', {retries: 3}, function () { + afterEach(function () { + cy.cleanTestFixture(wizardEditPath); + }); + + it('insert aem forms Wizard', function () { cy.cleanTest(wizardEditPath).then(function () { dropWizardInSites(); cy.deleteComponentByPath(wizardEditPath); }); }); - // adding retry, sometimes site editor does not load - it('open edit dialog of aem forms Wizard', {retries: 3}, function () { + it('open edit dialog of aem forms Wizard', function () { cy.cleanTest(wizardEditPath).then(function () { dropWizardInSites(); cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + wizardEditPathSelector); @@ -281,27 +288,26 @@ describe('Page - Authoring', function () { cy.get("[name='./dataRef']").should("exist"); cy.get("[name='./visible']").should("exist"); cy.get("[name='./enabled']").should("exist"); - cy.get('.cq-dialog-cancel').should('be.visible').click({force: true}).then(() => { + cy.cancelConfigureDialog().then(() => { cy.deleteComponentByPath(wizardEditPath); }) }); }); - it('open editable toolbar of 2nd wizard panel', {retries: 3}, function () { + it('open editable toolbar of 2nd wizard panel', function () { cy.cleanTest(wizardEditPath).then(function () { dropWizardInSites(); addComponentInWizardOfSites("Adaptive Form Number Input", afConstants.components.forms.resourceType.formnumberinput); addComponentInWizardOfSites("Adaptive Form Panel", afConstants.components.forms.resourceType.panelcontainer); - cy.wait(2000).then(() => { - cy.get("#sidepanel-toggle-button").click(); - cy.get('coral-tab[icon="layers"][aria-label="Content Tree"]').click(); - cy.get(`div[data-path='${wizardEditPath}']`).click({force: true}); - cy.get(`div[data-path='${panelcontainerPath}']`).click({force: true}); - cy.get('#EditableToolbar').should('be.visible'); - cy.get('body').click(0, 0); - cy.deleteComponentByPath(wizardEditPath); - }); + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + wizardEditPathSelector); + cy.invokeEditableAction(navigationPanelSelector); + cy.get("table.cmp-panelselector__table:visible").find(panelcontainerDataId).find("td").first() + .should('be.visible').click(); + cy.get('body').click(0, 0); + cy.openEditableToolbar(sitesSelectors.overlays.overlay.component + panelcontainerDataPath); + cy.get('#EditableToolbar button' + panelcontainerDataPath).should('be.visible'); + cy.deleteComponentByPath(wizardEditPath); }); });