diff --git a/.github/workflows/run-nala-default.yml b/.github/workflows/run-nala-default.yml new file mode 100644 index 0000000000..8dc51fc048 --- /dev/null +++ b/.github/workflows/run-nala-default.yml @@ -0,0 +1,48 @@ +name: Run Nala Tests + +on: + push: + branches: + - stage + - main + pull_request: + branches: + - stage + - main + types: [opened, synchronize, reopened] + +jobs: + run-nala-tests: + name: Running Nala E2E UI Tests + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20.x] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Set up Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Set execute permission for nalarun.sh + run: chmod +x ./nalarun.sh + + - name: Run Nala Tests via run.sh + run: ./nalarun.sh + env: + labels: ${{ join(github.event.pull_request.labels.*.name, ' ') }} + branch: ${{ github.event.pull_request.head.ref }} + repoName: ${{ github.repository }} + prUrl: ${{ github.event.pull_request.head.repo.html_url }} + prOrg: ${{ github.event.pull_request.head.repo.owner.login }} + prRepo: ${{ github.event.pull_request.head.repo.name }} + prBranch: ${{ github.event.pull_request.head.ref }} + prBaseBranch: ${{ github.event.pull_request.base.ref }} + GITHUB_ACTION_PATH: ${{ github.workspace }} + IMS_EMAIL: ${{ secrets.IMS_EMAIL }} + IMS_PASS: ${{ secrets.IMS_PASS }} diff --git a/nala/blocks/accordion/accordion.spec.js b/nala/blocks/accordion/accordion.spec.js index a40d8740e3..de3a6a19e5 100644 --- a/nala/blocks/accordion/accordion.spec.js +++ b/nala/blocks/accordion/accordion.spec.js @@ -11,7 +11,7 @@ module.exports = { heading1: 'What size PDFs can I compress?', heading2: 'How do I check my PDF file size?', }, - tags: '@accordion @t1 @smoke @regression @milo', + tags: '@accordion @smoke @regression @milo', }, { tcid: '1', diff --git a/nala/blocks/accordion/accordion.test.js b/nala/blocks/accordion/accordion.test.js index 86dfa7d275..55f083916e 100644 --- a/nala/blocks/accordion/accordion.test.js +++ b/nala/blocks/accordion/accordion.test.js @@ -1,3 +1,4 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ import { expect, test } from '@playwright/test'; import WebUtil from '../../libs/webutil.js'; import { features } from './accordion.spec.js'; diff --git a/nala/blocks/actionitem/actionitem.page.js b/nala/blocks/actionitem/actionitem.page.js new file mode 100644 index 0000000000..552c5ce9e1 --- /dev/null +++ b/nala/blocks/actionitem/actionitem.page.js @@ -0,0 +1,33 @@ +export default class ActionItem { + constructor(page, nth = 0) { + this.page = page; + + this.actionItem = this.page.locator('.action-item').nth(nth); + this.small = this.page.locator('.action-item.small').nth(nth); + this.medium = this.page.locator('.action-item.medium').nth(nth); + this.large = this.page.locator('.action-item.large').nth(nth); + this.center = this.page.locator('.action-item.center').nth(nth); + this.rounded = this.page.locator('.action-item.rounded').nth(nth); + this.actionItemFloat = this.page.locator('.action-item.float-button').nth(nth); + this.floatButton = this.page.locator('.action-item.float-button > div > div> p > a'); + this.libraryContainerStart = this.page.locator('.library-container-start').nth(nth); + this.libraryContainerEnd = this.page.locator('.library-container-end').nth(nth); + this.actionScroller = this.page.locator('.action-scroller').nth(nth); + this.scroller = this.page.locator('.scroller ').nth(nth); + this.scrollerActionItems = this.scroller.locator('.action-item'); + + this.navigationPrevious = this.actionScroller.locator('.nav-grad.previous'); + this.navigationNext = this.actionScroller.locator('.nav-grad.next'); + this.nextButton = this.navigationNext.locator('.nav-button.next-button'); + this.previousButton = this.navigationPrevious.locator('.nav-button.previous-button'); + + this.scrollerActionItems = this.scroller.locator('.action-item'); + + this.mainImage = this.actionItem.locator('.main-image').nth(nth); + this.mainImageDark = this.actionItem.locator('.main-image.dark').nth(nth); + this.image = this.mainImage.locator('img').nth(0); + this.bodyText = this.actionItem.locator('p').nth(1); + this.bodyTextLink = this.actionItem.locator('a').nth(0); + this.floatOutlineButton = this.mainImage.locator('a'); + } +} diff --git a/nala/blocks/actionitem/actionitem.spec.js b/nala/blocks/actionitem/actionitem.spec.js new file mode 100644 index 0000000000..54b8f92efe --- /dev/null +++ b/nala/blocks/actionitem/actionitem.spec.js @@ -0,0 +1,87 @@ +module.exports = { + FeatureName: 'Action Item Block', + features: [ + { + tcid: '0', + name: '@Action-item (small)', + path: '/drafts/nala/blocks/action-item/action-item-small', + data: { + bodyText: 'Body XS Regular - Image min-height 56px', + imgMinHeight: '56px', + }, + tags: '@action-item @smoke @regression @milo', + }, + { + tcid: '1', + name: '@Action-item (medium)', + path: '/drafts/nala/blocks/action-item/action-item-medium', + data: { + bodyText: 'Body S Regular - Image min-height 80px', + imgMinHeight: '80px', + }, + tags: '@action-item @smoke @regression @milo', + }, + { + tcid: '2', + name: '@Action-item (large)', + path: '/drafts/nala/blocks/action-item/action-item-large', + data: { + bodyText: 'Body M Regular - Image min-height 104px', + imgMinHeight: '104px', + }, + tags: '@action-item @smoke @regression @milo', + }, + { + tcid: '3', + name: '@Action-item (center)', + path: '/drafts/nala/blocks/action-item/action-item-center', + data: { + bodyText: 'Center content', + margin: '0 auto', + }, + tags: '@action-item @smoke @regression @milo', + }, + { + tcid: '4', + name: '@Action-item (rounded)', + path: '/drafts/nala/blocks/action-item/action-item-rounded', + data: { + bodyText: 'Border radius 4px', + borderRadius: '4px', + }, + tags: '@action-item @smoke @regression @milo', + }, + { + tcid: '5', + name: '@Action-item (float-button)', + path: '/drafts/nala/blocks/action-item/action-item-float-button', + data: { + bodyText: 'Float button', + floatButtonText: 'Edit', + }, + tags: '@action-item @smoke @regression @milo', + }, + { + tcid: '6', + name: '@Action-item (scroller)', + path: '/drafts/nala/blocks/action-item/action-scroller', + data: { + bodyText: 'content', + floatButtonText: 'Edit', + actionItemsCount: 6, + }, + tags: '@action-item @smoke @regression @milo', + }, + { + tcid: '7', + name: '@Action-item (scroller with navigation)', + path: '/drafts/nala/blocks/action-item/action-scroller-navigation', + data: { + bodyText: 'content', + floatButtonText: 'Edit', + actionItemsCount: 8, + }, + tags: '@action-item @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/actionitem/actionitem.test.js b/nala/blocks/actionitem/actionitem.test.js new file mode 100644 index 0000000000..e92f35cf4c --- /dev/null +++ b/nala/blocks/actionitem/actionitem.test.js @@ -0,0 +1,189 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import { features } from './actionitem.spec.js'; +import ActionItem from './actionitem.page.js'; + +let actionItem; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Action-Item block test suite', () => { + test.beforeEach(async ({ page }) => { + actionItem = new ActionItem(page); + }); + + // Test 0 : Action-Item (Small) + test(`0: @Action-item (small), ${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + const { data } = features[0]; + + await test.step('step-1: Go to Action item block test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Action item content/specs', async () => { + await expect(await actionItem.small).toBeVisible(); + await expect(await actionItem.image).toBeVisible(); + await expect(await actionItem.image).toHaveCSS('min-height', data.imgMinHeight); + + await expect(await actionItem.bodyTextLink).toBeVisible(); + await expect(await actionItem.bodyText).toContainText(data.bodyText); + }); + }); + + // Test 1 : Action-Item (Medium) + test(`1: @Action-item (medium), ${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + const { data } = features[1]; + + await test.step('step-1: Go to Action item block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Action item content/specs', async () => { + await expect(await actionItem.medium).toBeVisible(); + await expect(await actionItem.image).toBeVisible(); + await expect(await actionItem.image).toHaveCSS('min-height', data.imgMinHeight); + + await expect(await actionItem.bodyTextLink).toBeVisible(); + await expect(await actionItem.bodyText).toContainText(data.bodyText); + }); + }); + + // Test 2 : Action-Item (Large) + test(`2: @Action-item (large), ${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + const { data } = features[2]; + + await test.step('step-1: Go to Action item block test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Action item content/specs', async () => { + await expect(await actionItem.large).toBeVisible(); + await expect(await actionItem.image).toBeVisible(); + await expect(await actionItem.image).toHaveCSS('min-height', data.imgMinHeight); + + await expect(await actionItem.bodyTextLink).toBeVisible(); + await expect(await actionItem.bodyText).toContainText(data.bodyText); + }); + }); + + // Test 3 : Action-Item (Center) + test(`3: @Action-item (center), ${features[3].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[3].path}${miloLibs}`); + const { data } = features[3]; + + await test.step('step-1: Go to Action item block test page', async () => { + await page.goto(`${baseURL}${features[3].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[3].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Action item content/specs', async () => { + await expect(await actionItem.center).toBeVisible(); + await expect(await actionItem.image).toBeVisible(); + + await expect(await actionItem.bodyTextLink).toBeVisible(); + await expect(await actionItem.bodyText).toContainText(data.bodyText); + }); + }); + + // Test 4 : Action-Item (Rounded) + test(`4: @Action-item (rounded), ${features[4].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[4].path}${miloLibs}`); + const { data } = features[4]; + + await test.step('step-1: Go to Action item block test page', async () => { + await page.goto(`${baseURL}${features[4].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[4].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Action item content/specs', async () => { + await expect(await actionItem.rounded).toBeVisible(); + await expect(await actionItem.image).toBeVisible(); + await expect(await actionItem.image).toHaveCSS('border-radius', data.borderRadius); + + await expect(await actionItem.bodyTextLink).toBeVisible(); + await expect(await actionItem.bodyText).toContainText(data.bodyText); + }); + }); + + // Test 5 : Action-Item (Float Button) + test(`5: @Action-item (float-button), ${features[5].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[5].path}${miloLibs}`; + console.info(`[Test Page]: ${testPage}`); + const { data } = features[5]; + + await test.step('step-1: Go to Action item block test page', async () => { + await page.goto(`${baseURL}${features[5].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[5].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Action item content/specs', async () => { + await expect(await actionItem.actionItemFloat).toBeVisible(); + await expect(await actionItem.image).toBeVisible(); + await expect(await actionItem.floatOutlineButton).toBeVisible(); + await expect(await actionItem.floatOutlineButton).toContainText(data.floatButtonText); + }); + await test.step('step-3: Click the float button', async () => { + await actionItem.floatButton.click(); + expect(await page.url()).not.toBe(testPage); + }); + }); + + // Test 6 : Action-Item (scroller) + test(`6: @Action-item (scroller), ${features[6].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[6].path}${miloLibs}`); + const { data } = features[6]; + + await test.step('step-1: Go to Action item block test page', async () => { + await page.goto(`${baseURL}${features[6].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[6].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Action item content/specs', async () => { + await expect(await actionItem.actionScroller).toBeVisible(); + await expect(await actionItem.scroller).toBeVisible(); + await expect(await actionItem.scrollerActionItems).toHaveCount(data.actionItemsCount); + + await expect(await actionItem.image).toBeVisible(); + await expect(await actionItem.bodyText).toContainText(data.bodyText); + }); + }); + + // Test 7 : Action-Item (scroller) + test(`7: @Action-item (scroller with navigation), ${features[7].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[7].path}${miloLibs}`); + const { data } = features[7]; + + await test.step('step-1: Go to Action item block test page', async () => { + await page.goto(`${baseURL}${features[7].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[7].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Action item content/specs', async () => { + await expect(await actionItem.actionScroller).toBeVisible(); + await expect(await actionItem.scroller).toBeVisible(); + await expect(await actionItem.scrollerActionItems).toHaveCount(data.actionItemsCount); + + await expect(await actionItem.image).toBeVisible(); + await expect(await actionItem.bodyText).toContainText(data.bodyText); + + await expect(await actionItem.nextButton).toBeVisible({ timeout: 1000 }); + await actionItem.nextButton.click(); + await expect(await actionItem.previousButton).toBeVisible({ timeout: 1000 }); + await expect(await actionItem.navigationNext).toHaveAttribute('hide-btn', 'false'); + }); + }); +}); diff --git a/nala/blocks/aside/aside.page.js b/nala/blocks/aside/aside.page.js new file mode 100644 index 0000000000..15cc507475 --- /dev/null +++ b/nala/blocks/aside/aside.page.js @@ -0,0 +1,65 @@ +/* eslint-disable import/no-import-module-exports */ + +export default class Aside { + constructor(page) { + this.page = page; + + this.aside = page.locator('.aside'); + this.textField = this.aside.locator('.text'); + this.textFieldSmall = this.aside.locator('p.body-s').first(); + this.textFieldMedium = this.aside.locator('p.body-m').first(); + this.textFieldLarge = this.aside.locator('p.body-l').first(); + // ASIDE HEADINGS: + this.h2TitleXLarge = this.aside.locator('h2.heading-xl'); + this.h3TitleXLarge = this.aside.locator('h3.heading-xl'); + this.h2TitleLarge = this.aside.locator('h2.heading-l'); + this.h3TitleLarge = this.aside.locator('h3.heading-l'); + this.h2TitleSmall = this.aside.locator('h2.heading-s'); + this.h3TitleSmall = this.aside.locator('h3.heading-s'); + // ASIDE BLOCK ELEMENTS: + this.icon = this.aside.locator('p.icon-area picture'); + this.image = this.aside.locator('div.image'); + this.noImage = this.aside.locator('div.no-image'); + this.iconArea = this.aside.locator('p.icon-area'); + this.detailLabel = this.aside.locator('p.detail-m'); + this.actionArea = this.aside.locator('p.action-area'); + this.textLink = this.textField.locator('a').first(); + this.linkTextCta = this.aside.locator('a[daa-ll*="Link"], a[daa-ll*="link"], a[daa-ll*="action"]'); + this.actionLinks = this.aside.locator('div[data-valign="middle"] a'); + this.actionButtons = this.aside.locator('p.action-area a'); + this.blueButtonCta = this.aside.locator('a.con-button.blue'); + this.blackButtonCta = this.aside.locator('a.con-button.outline'); + // ASIDE DEFAULT BLOCKS: + this.asideSmall = page.locator('div.aside.small'); + this.asideMedium = page.locator('div.aside.medium'); + this.asideLarge = page.locator('div.aside.large'); + // ASIDE INLINE BLOCKS: + this.asideInline = page.locator('div.aside.inline'); + this.asideInlineDark = page.locator('div.aside.inline.dark'); + // ASIDE SPLIT BLOCKS: + this.asideSplitSmallDark = page.locator('div.aside.split.small.dark'); + this.asideSplitSmallHalfDark = page.locator('div.aside.split.small.half.dark'); + this.asideSplitMedium = page.locator('div.aside.split.medium'); + this.asideSplitMedidumHalf = page.locator('div.aside.split.medium.half'); + this.asideSplitLarge = page.locator('div.aside.split.large'); + this.asideSplitLargeHalfDark = page.locator('div.aside.split.large.half.dark'); + // ASIDE NOTIFICATION BLOCKS: + this.asideNotifSmall = page.locator('div.aside.notification.small'); + this.asideNotifMedium = page.locator('div.aside.notification.medium'); + this.asideNotifLarge = page.locator('div.aside.notification.large'); + this.asideNotifMediumCenter = page.locator('div.aside.notification.center'); + this.asideNotifLargeCenter = page.locator('div.aside.notification.large.center'); + this.asideNotifExtraSmallDark = page.locator('div.aside.notification.extra-small.dark'); + + // ASIDE PROPS: + this.props = { + background: { + black: 'rgb(17, 17, 17)', + darkGrey: 'rgb(171, 171, 171)', + lightGrey1: 'rgb(238, 238, 238)', + lightGrey2: 'rgb(245, 245, 245)', + lightGrey3: 'rgb(249, 249, 249)', + }, + }; + } +} diff --git a/nala/blocks/aside/aside.spec.js b/nala/blocks/aside/aside.spec.js new file mode 100644 index 0000000000..aa73285964 --- /dev/null +++ b/nala/blocks/aside/aside.spec.js @@ -0,0 +1,107 @@ +module.exports = { + name: 'Aside Block', + features: [ + { + name: '@Aside-Small', + path: '/drafts/nala/blocks/aside/aside-small', + browserParams: '?georouting=off', + tags: '@aside @aside-small @smoke @regression @milo', + }, + { + name: '@Aside-Medium', + path: '/drafts/nala/blocks/aside/aside-medium', + browserParams: '?georouting=off', + tags: '@aside @aside-medium @smoke @regression @milo', + }, + { + name: '@Aside-Large', + path: '/drafts/nala/blocks/aside/aside-large', + browserParams: '?georouting=off', + tags: '@aside @aside-large @smoke @regression @milo', + }, + { + name: '@Aside-Split-Small-Dark', + path: '/drafts/nala/blocks/aside/aside-split-small-dark', + browserParams: '?georouting=off', + tags: '@aside @aside-split-small-dark @smoke @regression @milo', + }, + { + name: '@Aside-Split-Small-Half-Dark', + path: '/drafts/nala/blocks/aside/aside-split-small-half-dark', + browserParams: '?georouting=off', + tags: '@aside @aside-split-small-half-dark @smoke @regression @milo', + }, + { + name: '@Aside-Split-Medium', + path: '/drafts/nala/blocks/aside/aside-split-medium', + browserParams: '?georouting=off', + tags: '@aside @aside-split-medium @smoke @regression @milo', + }, + { + name: '@Aside-Split-Medium-Half', + path: '/drafts/nala/blocks/aside/aside-split-medium-half', + browserParams: '?georouting=off', + tags: '@aside @aside-split-medium-half @smoke @regression @milo', + }, + { + name: '@Aside-Split-Large', + path: '/drafts/nala/blocks/aside/aside-split-large', + browserParams: '?georouting=off', + tags: '@aside @aside-split-large @smoke @regression @milo', + }, + { + name: '@Aside-Split-Large-Half-Dark', + path: '/drafts/nala/blocks/aside/aside-split-large-half-dark', + browserParams: '?georouting=off', + tags: '@aside @aside-split-large-half-dark @smoke @regression @milo', + }, + { + name: '@Aside-Inline', + path: '/drafts/nala/blocks/aside/aside-inline', + browserParams: '?georouting=off', + tags: '@aside @aside-inline @smoke @regression @milo', + }, + { + name: '@Aside-Inline-Dark', + path: '/drafts/nala/blocks/aside/aside-inline-dark', + browserParams: '?georouting=off', + tags: '@aside @aside-inline-dark @smoke @regression @milo', + }, + { + name: '@Aside-Notif-Extra-Small-Dark', + path: '/drafts/nala/blocks/aside/aside-notification-extrasmall-dark', + browserParams: '?georouting=off', + tags: '@aside @aside-notif-extra-small-dark @smoke @regression @milo', + }, + { + name: '@Aside-Notif-Small', + path: '/drafts/nala/blocks/aside/aside-notification-small', + browserParams: '?georouting=off', + tags: '@aside @aside-notif-small @smoke @regression @milo', + }, + { + name: '@Aside-Notif-Medium', + path: '/drafts/nala/blocks/aside/aside-notification-medium', + browserParams: '?georouting=off', + tags: '@aside @aside-notif-medium @smoke @regression @milo', + }, + { + name: '@Aside-Notif-Medium-Center', + path: '/drafts/nala/blocks/aside/aside-notification-medium-center', + browserParams: '?georouting=off', + tags: '@aside @aside-notif-medium-center @smoke @regression @milo', + }, + { + name: '@Aside-Notif-Large', + path: '/drafts/nala/blocks/aside/aside-notification-large', + browserParams: '?georouting=off', + tags: '@aside @aside-notif-large @smoke @regression @milo', + }, + { + name: '@Aside-Notif-Large-Center', + path: '/drafts/nala/blocks/aside/aside-notification-large-center', + browserParams: '?georouting=off', + tags: '@aside @aside-notif-large-center @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/aside/aside.test.js b/nala/blocks/aside/aside.test.js new file mode 100644 index 0000000000..4e019ba34f --- /dev/null +++ b/nala/blocks/aside/aside.test.js @@ -0,0 +1,526 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import { features } from './aside.spec.js'; +import AsideBlock from './aside.page.js'; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Aside Block test suite', () => { + // Aside Small Checks: + test(`${features[0].name}, ${features[0].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[0].path}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[0].path}${features[0].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${features[0].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideSmall).toBeVisible(); + await expect(Aside.icon).toBeVisible(); + await expect(Aside.image).toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).toBeVisible(); + await expect(Aside.h2TitleXLarge).toBeVisible(); + await expect(Aside.textFieldSmall).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.textLink).toBeVisible(); + await expect(Aside.blueButtonCta).toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(2); + // Check Aside block background: + const bgdColor = await Aside.asideSmall.evaluate( + (e) => window.getComputedStyle(e).getPropertyValue('background-color'), + ); + expect(bgdColor).toBe(Aside.props.background.lightGrey1); + }); + }); + + // Aside Medium Checks: + test(`${features[1].name}, ${features[1].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[1].path}${features[1].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${features[1].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideMedium).toBeVisible(); + await expect(Aside.icon).toBeVisible(); + await expect(Aside.image).toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).toBeVisible(); + await expect(Aside.h3TitleXLarge).toBeVisible(); + await expect(Aside.textFieldSmall).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.blueButtonCta).not.toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + await expect(Aside.linkTextCta).toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(2); + // Check Aside block background: + const bgdColor = await Aside.asideMedium.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.lightGrey1); + }); + }); + + // Aside Large Checks: + test(`${features[2].name}, ${features[2].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[2].path}${features[2].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${features[2].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideLarge).toBeVisible(); + await expect(Aside.icon).toBeVisible(); + await expect(Aside.image).toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).toBeVisible(); + await expect(Aside.h3TitleXLarge).toBeVisible(); + await expect(Aside.textFieldSmall).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.blueButtonCta).toBeVisible(); + await expect(Aside.blackButtonCta).not.toBeVisible(); + await expect(Aside.linkTextCta).toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(2); + // Check Aside block background: + const bgdColor = await Aside.asideLarge.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.lightGrey1); + }); + }); + + // Aside Split Small Dark Checks: + test(`${features[3].name}, ${features[3].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[3].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[3].path}${features[3].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[3].path}${features[3].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideSplitSmallDark).toBeVisible(); + await expect(Aside.icon).not.toBeVisible(); + await expect(Aside.image).toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).toBeVisible(); + await expect(Aside.h3TitleXLarge).toBeVisible(); + await expect(Aside.textFieldSmall).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.blueButtonCta).toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + await expect(Aside.linkTextCta).not.toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(2); + // Check Aside block background: + const bgdColor = await Aside.asideSplitSmallDark.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.black); + }); + }); + + // Aside Split Small Half Dark Checks: + test(`${features[4].name}, ${features[4].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[4].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[4].path}${features[4].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[4].path}${features[4].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideSplitSmallHalfDark).toBeVisible(); + await expect(Aside.icon).not.toBeVisible(); + await expect(Aside.image).toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).toBeVisible(); + await expect(Aside.h3TitleXLarge).toBeVisible(); + await expect(Aside.textFieldSmall).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.blueButtonCta).toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + await expect(Aside.linkTextCta).not.toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(2); + // Check Aside block background: + const bgdColor = await Aside.asideSplitSmallHalfDark.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.black); + }); + }); + + // Aside Split Medium Checks: + test(`${features[5].name}, ${features[5].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[5].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[5].path}${features[5].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[5].path}${features[5].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideSplitMedium).toBeVisible(); + await expect(Aside.icon).not.toBeVisible(); + await expect(Aside.image).toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).toBeVisible(); + await expect(Aside.h3TitleXLarge).toBeVisible(); + await expect(Aside.textFieldSmall).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.blueButtonCta).toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + await expect(Aside.linkTextCta).not.toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(2); + // Check Aside block background: + const bgdColor = await Aside.asideSplitMedium.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.lightGrey3); + }); + }); + + // Aside Split Medium Half Checks: + test(`${features[6].name}, ${features[6].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[6].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[6].path}${features[6].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[6].path}${features[6].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideSplitMedidumHalf).toBeVisible(); + await expect(Aside.icon).not.toBeVisible(); + await expect(Aside.image).toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).toBeVisible(); + await expect(Aside.h3TitleXLarge).toBeVisible(); + await expect(Aside.textFieldSmall).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.blueButtonCta).toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + await expect(Aside.linkTextCta).not.toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(2); + // Check Aside block background: + const bgdColor = await Aside.asideSplitMedidumHalf.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.lightGrey3); + }); + }); + + // Aside Split Large Checks: + test(`${features[7].name}, ${features[7].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[7].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[7].path}${features[7].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[7].path}${features[7].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideSplitLarge).toBeVisible(); + await expect(Aside.icon).not.toBeVisible(); + await expect(Aside.image).toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).toBeVisible(); + await expect(Aside.h3TitleXLarge).toBeVisible(); + await expect(Aside.textFieldSmall).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.blueButtonCta).toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + await expect(Aside.linkTextCta).not.toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(2); + // !Note: Aside Split Large doesn't have default background! + }); + }); + + // Aside Split Large Half Dark Checks: + test(`${features[8].name}, ${features[8].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[8].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[8].path}${features[8].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[8].path}${features[8].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideSplitLargeHalfDark).toBeVisible(); + await expect(Aside.icon).not.toBeVisible(); + await expect(Aside.image).toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).toBeVisible(); + await expect(Aside.h3TitleXLarge).toBeVisible(); + await expect(Aside.textFieldSmall).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.blueButtonCta).toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + await expect(Aside.linkTextCta).not.toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(2); + // Check Aside block background: + const bgdColor = await Aside.asideSplitLargeHalfDark.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.black); + }); + }); + + // Aside Inline Checks: + test(`${features[9].name}, ${features[9].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[9].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[9].path}${features[9].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[9].path}${features[9].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideInline).toBeVisible(); + await expect(Aside.icon).not.toBeVisible(); + await expect(Aside.image).toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).not.toBeVisible(); + await expect(Aside.h3TitleSmall).toBeVisible(); + await expect(Aside.textFieldMedium).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.blueButtonCta).not.toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + await expect(Aside.linkTextCta).not.toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(1); + // Check Aside block background: + const bgdColor = await Aside.asideInline.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.lightGrey2); + }); + }); + + // Aside Inline Dark Checks: + test(`${features[10].name}, ${features[10].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[10].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[10].path}${features[10].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[10].path}${features[10].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideInline).toBeVisible(); + await expect(Aside.icon).not.toBeVisible(); + await expect(Aside.image).toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).not.toBeVisible(); + await expect(Aside.h3TitleSmall).toBeVisible(); + await expect(Aside.textFieldMedium).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.blueButtonCta).not.toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + await expect(Aside.linkTextCta).not.toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(1); + // Check Aside block background: + const bgdColor = await Aside.asideInline.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.black); + }); + }); + + // Aside Notification Extra Small Dark: + test(`${features[11].name}, ${features[11].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[11].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[11].path}${features[11].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[11].path}${features[11].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideNotifExtraSmallDark).toBeVisible(); + await expect(Aside.icon).not.toBeVisible(); + await expect(Aside.noImage).toBeVisible(); + await expect(Aside.actionArea).not.toBeVisible(); + await expect(Aside.detailLabel).not.toBeVisible(); + await expect(Aside.h2TitleSmall).not.toBeVisible(); + await expect(Aside.h2TitleXLarge).not.toBeVisible(); + await expect(Aside.h3TitleSmall).not.toBeVisible(); + await expect(Aside.h3TitleXLarge).not.toBeVisible(); + await expect(Aside.textFieldSmall).not.toBeVisible(); + await expect(Aside.textFieldMedium).not.toBeVisible(); + await expect(Aside.textFieldLarge).not.toBeVisible(); + // Check Aside block buttons: + await expect(Aside.linkTextCta.first()).toBeVisible(); + await expect(Aside.blueButtonCta).not.toBeVisible(); + await expect(Aside.blackButtonCta).not.toBeVisible(); + expect(await Aside.actionLinks.count()).toEqual(2); + // Check Aside block background: + const bgdColor = await Aside.asideNotifExtraSmallDark.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.black); + }); + }); + + // Aside Notification Small: + test(`${features[12].name}, ${features[12].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[12].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[12].path}${features[12].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[12].path}${features[12].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideNotifSmall).toBeVisible(); + await expect(Aside.icon).toBeVisible(); + await expect(Aside.image).not.toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).not.toBeVisible(); + await expect(Aside.h2TitleSmall).not.toBeVisible(); + await expect(Aside.h2TitleXLarge).not.toBeVisible(); + await expect(Aside.h3TitleSmall).not.toBeVisible(); + await expect(Aside.h3TitleXLarge).not.toBeVisible(); + await expect(Aside.textFieldMedium).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.textLink).toBeVisible(); + await expect(Aside.blueButtonCta).not.toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(1); + // Check Aside block background: + const bgdColor = await Aside.asideNotifSmall.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.darkGrey); + }); + }); + + // Aside Notification Medium: + test(`${features[13].name}, ${features[13].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[13].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[13].path}${features[13].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[13].path}${features[13].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideNotifMedium).toBeVisible(); + await expect(Aside.icon).not.toBeVisible(); + await expect(Aside.image).toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).not.toBeVisible(); + await expect(Aside.h3TitleSmall).toBeVisible(); + await expect(Aside.textFieldSmall).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.linkTextCta).toBeVisible(); + await expect(Aside.blueButtonCta).not.toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(1); + // Check Aside block background: + const bgdColor = await Aside.asideNotifMedium.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.darkGrey); + }); + }); + + // Aside Notification Medium Center: + test(`${features[14].name}, ${features[14].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[14].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[14].path}${features[14].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[14].path}${features[14].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideNotifMedium).toBeVisible(); + await expect(Aside.icon).not.toBeVisible(); + await expect(Aside.image).not.toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).not.toBeVisible(); + await expect(Aside.h3TitleSmall).toBeVisible(); + await expect(Aside.textFieldSmall).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.linkTextCta).toBeVisible(); + await expect(Aside.blueButtonCta).not.toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(1); + // Check Aside block background: + const bgdColor = await Aside.asideNotifMedium.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.darkGrey); + }); + }); + + // Aside Notification Large: + test(`${features[15].name}, ${features[15].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[15].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[15].path}${features[15].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[15].path}${features[15].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideNotifLarge).toBeVisible(); + await expect(Aside.icon).not.toBeVisible(); + await expect(Aside.image).toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).not.toBeVisible(); + await expect(Aside.h3TitleLarge).toBeVisible(); + await expect(Aside.textFieldMedium).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.linkTextCta).toBeVisible(); + await expect(Aside.blueButtonCta).not.toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(1); + // Check Aside block background: + const bgdColor = await Aside.asideNotifLarge.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.darkGrey); + }); + }); + + // Aside Notification Large Center: + test(`${features[16].name}, ${features[16].tags}`, async ({ page, baseURL }) => { + const Aside = new AsideBlock(page); + console.info(`[Test Page]: ${baseURL}${features[16].path}${miloLibs}`); + + await test.step('Navigate to page with Aside block', async () => { + await page.goto(`${baseURL}${features[16].path}${features[16].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[16].path}${features[16].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Aside block content', async () => { + await expect(Aside.asideNotifLargeCenter).toBeVisible(); + await expect(Aside.icon).not.toBeVisible(); + await expect(Aside.image).not.toBeVisible(); + await expect(Aside.actionArea).toBeVisible(); + await expect(Aside.detailLabel).not.toBeVisible(); + await expect(Aside.h3TitleLarge).toBeVisible(); + await expect(Aside.textFieldMedium).toBeVisible(); + // Check Aside block buttons: + await expect(Aside.linkTextCta).toBeVisible(); + await expect(Aside.blueButtonCta).not.toBeVisible(); + await expect(Aside.blackButtonCta).toBeVisible(); + expect(await Aside.actionButtons.count()).toEqual(1); + // Check Aside block background: + const bgdColor = await Aside.asideNotifLargeCenter.evaluate((e) => window.getComputedStyle(e).getPropertyValue('background-color')); + expect(bgdColor).toBe(Aside.props.background.darkGrey); + }); + }); +}); diff --git a/nala/blocks/card/card.page.js b/nala/blocks/card/card.page.js new file mode 100644 index 0000000000..3e634e38ca --- /dev/null +++ b/nala/blocks/card/card.page.js @@ -0,0 +1,57 @@ +export default class Card { + constructor(page, nth = 0) { + this.page = page; + // card locators + this.card = this.page.locator('.card').nth(nth); + + // One half card locators + this.oneHalfCard = this.page.locator('.consonant-OneHalfCard').nth(nth); + this.oneHalfCardImage = this.oneHalfCard.locator('.consonant-OneHalfCard-img'); + this.oneHalfCardInner = this.oneHalfCard.locator('.consonant-OneHalfCard-inner'); + this.oneHalfCardTitleH3 = this.oneHalfCard.locator('h3.consonant-OneHalfCard-title'); + this.oneHalfCardText = this.oneHalfCard.locator('.consonant-OneHalfCard-text'); + // Double width card locators + this.doubleWidthCard = this.page.locator('.double-width-card').nth(nth); + this.doubleWidthCardImage = this.doubleWidthCard.locator('.consonant-DoubleWideCard-img'); + this.doubleWidthCardInner = this.doubleWidthCard.locator('.consonant-DoubleWideCard-inner'); + this.doubleWidthCardTitleH3 = this.doubleWidthCard.locator('h3.consonant-DoubleWideCard-title'); + this.doubleWidthCardText = this.doubleWidthCard.locator('.consonant-DoubleWideCard-text'); + // Product card locators + this.productCard = this.page.locator('.product-card').nth(nth); + this.productCardImage = this.productCard.locator('.consonant-ProductCard-img'); + this.productCardInner = this.productCard.locator('.consonant-ProductCard-inner'); + this.productCardTitleH3 = this.productCard.locator('h3.consonant-ProductCard-title'); + this.productCardText = this.productCard.locator('.consonant-ProductCard-text'); + // Half height card locators + this.halfHeightCard = this.page.locator('.half-height-card').nth(nth); + this.halfHeightCardImage = this.halfHeightCard.locator('.consonant-HalfHeightCard-img'); + this.halfHeightCardLink = this.halfHeightCard.locator('a.consonant-HalfHeightCard'); + this.halfHeightCardInner = this.halfHeightCard.locator('.consonant-HalfHeightCard-inner'); + this.halfHeightCardTitleH3 = this.halfHeightCard.locator('h3.consonant-HalfHeightCard-title'); + this.halfHeightCardText = this.halfHeightCard.locator('.consonant-HalfHeightCard-text'); + // Horizontal card locators + this.horizontalCard = this.page.locator('.card-horizontal').nth(nth); + this.horizontalCardImage = this.horizontalCard.locator('.card-image'); + this.horizontalCardImg = this.horizontalCard.locator('img'); + this.horizontalCardContent = this.horizontalCard.locator('card-content'); + this.horizontalCardBodyXS = this.horizontalCard.locator('.body-xs'); + this.horizontalCardHeadingXS = this.horizontalCard.locator('h2.heading-xs'); + this.horizontalCardHeadingXSLink = this.horizontalCardHeadingXS.locator('a'); + + // card footer sections + this.footer = this.card.locator('.consonant-CardFooter'); + this.footerOutlineButton = this.card.locator('a.con-button.outline').nth(0); + this.footerOutlineButton2 = this.card.locator('a.con-button.outline').nth(1); + this.footerBlueButton = this.card.locator('a.con-button.blue').nth(0); + this.footerBlueButton2 = this.card.locator('a.con-button.blue').nth(1); + + // card attributes + this.attributes = { + oneHalfCardImage: { style: 'background-image: url' }, + 'card-image': { + loading: 'eager', + fetchpriority: 'hight', + }, + }; + } +} diff --git a/nala/blocks/card/card.spec.js b/nala/blocks/card/card.spec.js new file mode 100644 index 0000000000..bafea2898f --- /dev/null +++ b/nala/blocks/card/card.spec.js @@ -0,0 +1,84 @@ +/* eslint-disable max-len */ + +module.exports = { + FeatureName: 'Consonant Card Block', + features: [ + { + tcid: '0', + name: '@Card', + path: '/drafts/nala/blocks/card/card', + data: { + titleH3: 'Lorem ipsum dolor sit amet', + text: 'Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis', + footerOutlineButtonText: 'Sign up', + footerBlueButtonText: 'Learn more', + }, + tags: '@card @smoke @regression @milo', + }, + { + tcid: '1', + name: '@Card (half-card, border)', + path: '/drafts/nala/blocks/card/half-card-border', + data: { + titleH3: 'Lorem ipsum dolor sit amet', + text: 'Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis', + footerOutlineButtonText: 'Sign up', + footerBlueButtonText: 'Learn more', + }, + tags: '@card @smoke @regression @milo', + }, + { + tcid: '2', + name: '@Card (double-width-card, border)', + path: '/drafts/nala/blocks/card/double-width-card-border', + data: { + titleH3: 'Lorem ipsum dolor sit amet', + text: 'Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis', + }, + tags: '@card @smoke @regression @milo', + }, + { + tcid: '3', + name: '@Card (product-card, border) ', + path: '/drafts/nala/blocks/card/product-card-border', + data: { + titleH3: 'Lorem ipsum dolor sit amet', + text: 'Maecenas porttitor congue massa. Fusce posuere, magna sed pulvinar ultricies, purus lectus malesuada libero, sit amet commodo magna eros quis', + footerOutlineButtonText: 'Learn more', + footerBlueButtonText: 'Sign up', + }, + tags: '@card @smoke @regression @milo', + }, + { + tcid: '4', + name: '@Card (half-height-card, border)', + path: '/drafts/nala/blocks/card/half-height-card-border', + data: { titleH3: 'Lorem ipsum dolor sit amet' }, + tags: '@card @smoke @regression @milo', + }, + { + tcid: '5', + name: '@Card-horizontal', + path: '/drafts/nala/blocks/card/card-horizontal', + data: { + bodyXS: 'Body XS Regular', + headingXS: 'Heading XS Bold Lorem ipsum dolo sit amet, consectetur adipis cing elit.', + imgWidth: '180', + imgHeight: '132', + }, + tags: '@card @smoke @regression @milo', + }, + { + tcid: '6', + name: '@Card-horizontal (tile)', + path: '/drafts/nala/blocks/card/card-horizontal-tile', + data: { + bodyXS: 'Body XS Regular', + headingXS: 'Heading XS Bold Lorem ipsum dolo sit amet, consectetur adipis cing elit.', + imgWidth: '80', + imgHeight: '78', + }, + tags: '@card @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/card/card.test.js b/nala/blocks/card/card.test.js new file mode 100644 index 0000000000..9203b90d1a --- /dev/null +++ b/nala/blocks/card/card.test.js @@ -0,0 +1,193 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import { features } from './card.spec.js'; +import ConsonantCard from './card.page.js'; + +let card; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Consonant card feature test suite', () => { + test.beforeEach(async ({ page }) => { + card = new ConsonantCard(page); + }); + + // Test 0 : Card + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + const { data } = features[0]; + + await test.step('step-1: Go to Consonant Card feature test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Card content/specs', async () => { + await expect(await card.oneHalfCard).toBeVisible(); + await expect(await card.oneHalfCardImage).toBeVisible(); + + await expect(await card.oneHalfCardTitleH3).toContainText(data.titleH3); + await expect(await card.oneHalfCardText).toContainText(data.text); + + await expect(await card.footer).toBeVisible(); + await expect(await card.footerOutlineButton).toBeVisible(); + await expect(await card.footerOutlineButton).toContainText(data.footerOutlineButtonText); + + await expect(await card.footerBlueButton).toBeVisible(); + await expect(await card.footerBlueButton).toContainText(data.footerBlueButtonText); + }); + }); + + // Test 1 : Card (half-card, border) + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + const { data } = features[1]; + + await test.step('step-1: Go to Consonant Card feature test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Half Card with Boarder content/specs', async () => { + await expect(await card.oneHalfCard).toBeVisible(); + + expect(await card.oneHalfCard.getAttribute('class')).toContain('border'); + + await expect(await card.oneHalfCardImage).toBeVisible(); + await expect(await card.oneHalfCardTitleH3).toContainText(data.titleH3); + await expect(await card.oneHalfCardText).toContainText(data.text); + + await expect(await card.footer).toBeVisible(); + await expect(await card.footerOutlineButton).toBeVisible(); + await expect(await card.footerOutlineButton).toContainText(data.footerOutlineButtonText); + + await expect(await card.footerBlueButton).toBeVisible(); + await expect(await card.footerBlueButton).toContainText(data.footerBlueButtonText); + }); + }); + + // Test 2 : card (double-width-card, border) + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + const { data } = features[2]; + + await test.step('step-2: Go to Consonant Card feature test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify card (double-width-card, border) content/specs', async () => { + await expect(await card.doubleWidthCard).toBeVisible(); + + expect(await card.doubleWidthCard.getAttribute('class')).toContain('border'); + + await expect(await card.doubleWidthCardImage).toBeVisible(); + await expect(await card.doubleWidthCardTitleH3).toContainText(data.titleH3); + await expect(await card.doubleWidthCardText).toContainText(data.text); + }); + }); + + // Test 3 : Card (product-card, border) + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[3].path}${miloLibs}`); + const { data } = features[3]; + + await test.step('step-2: Go to Consonant Card feature test page', async () => { + await page.goto(`${baseURL}${features[3].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[3].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Card (product-card, border) content/specs', async () => { + await expect(await card.productCard).toBeVisible(); + + expect(await card.productCard.getAttribute('class')).toContain('border'); + + await expect(await card.productCardTitleH3).toContainText(data.titleH3); + await expect(await card.productCardText).toContainText(data.text); + + await expect(await card.footer).toBeVisible(); + await expect(await card.footerOutlineButton).toBeVisible(); + await expect(await card.footerOutlineButton).toContainText(data.footerOutlineButtonText); + + await expect(await card.footerBlueButton).toBeVisible(); + await expect(await card.footerBlueButton).toContainText(data.footerBlueButtonText); + }); + }); + + // Test 4 : Card (half-height-card, border) + test(`${features[4].name},${features[4].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[4].path}${miloLibs}`); + const { data } = features[4]; + + await test.step('step-2: Go to Consonant Card feature test page', async () => { + await page.goto(`${baseURL}${features[4].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[4].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Card (half-height-card, border) content/specs', async () => { + await expect(await card.halfHeightCard).toBeVisible(); + + expect(await card.halfHeightCard.getAttribute('class')).toContain('border'); + + await expect(await card.halfHeightCardImage).toBeVisible(); + await expect(await card.halfHeightCardLink).toBeVisible(); + + await expect(await card.halfHeightCardTitleH3).toContainText(data.titleH3); + }); + }); + + // Test 5 : Card-horizontal + test(`${features[5].name},${features[5].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[5].path}${miloLibs}`); + const { data } = features[5]; + + await test.step('step-2: Go to Consonant Card feature test page', async () => { + await page.goto(`${baseURL}${features[5].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[5].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Card-horizontal content/specs', async () => { + await expect(await card.horizontalCard).toBeVisible(); + await expect(await card.horizontalCardImage).toBeVisible(); + + expect(await card.horizontalCardImg.getAttribute('width')).toContain(data.imgWidth); + expect(await card.horizontalCardImg.getAttribute('height')).toContain(data.imgHeight); + + await expect(await card.horizontalCardBodyXS).toContainText(data.bodyXS); + await expect(await card.horizontalCardHeadingXS).toContainText(data.headingXS); + await expect(await card.horizontalCardHeadingXSLink).toContainText(data.headingXS); + }); + }); + + // Test 6 : Card-horizontal (tile) + test(`${features[6].name},${features[6].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[6].path}${miloLibs}`); + const { data } = features[6]; + + await test.step('step-2: Go to Consonant Card feature test page', async () => { + await page.goto(`${baseURL}${features[6].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[6].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Card-horizontal (tile) content/specs', async () => { + await expect(await card.horizontalCard).toBeVisible(); + expect(await card.horizontalCard.getAttribute('class')).toContain('tile'); + + await expect(await card.horizontalCardImage).toBeVisible(); + + expect(await card.horizontalCardImg.getAttribute('width')).toContain(data.imgWidth); + expect(await card.horizontalCardImg.getAttribute('height')).toContain(data.imgHeight); + + await expect(await card.horizontalCardBodyXS).toContainText(data.bodyXS); + await expect(await card.horizontalCardHeadingXS).toContainText(data.headingXS); + await expect(await card.horizontalCardHeadingXSLink).toContainText(data.headingXS); + }); + }); +}); diff --git a/nala/blocks/carousel/carousel.page.js b/nala/blocks/carousel/carousel.page.js new file mode 100644 index 0000000000..e121a54d69 --- /dev/null +++ b/nala/blocks/carousel/carousel.page.js @@ -0,0 +1,222 @@ +export default class Carousel { + constructor(page) { + this.page = page; + // carousel types selectors + this.carouselContainer = page.locator('.carousel.container'); + this.carouselLightbox = page.locator('.carousel.lightbox'); + this.carouselContainerShow2 = page.locator('.carousel.show-2.container'); + this.carousel = page.locator('.carousel'); + + // carousel selectors + this.slides = this.carousel.locator('.carousel-slides'); + this.activeSlide = this.slides.locator('.section.carousel-slide.active'); + this.slidesCount = this.slides.locator('.section.carousel-slide'); + this.indicator = this.carousel.locator('.carousel-indicators'); + this.indicatorCount = this.indicator.locator('.carousel-indicator'); + this.activeIndicator = this.indicator.locator('.carousel-indicator.active'); + this.nextButton = this.carousel.locator('.carousel-next'); + this.previousButton = this.carousel.locator('.carousel-previous'); + this.lightboxExpandButton = this.carouselLightbox.locator('.lightbox-button.carousel-expand'); + this.lightboxCloseButton = this.carouselLightbox.locator('.lightbox-button.carousel-close'); + } + + /** + * Get the index of the current slide. + * @return {Promise}. + */ + async getCurrentSlideIndex() { + const currentIndex = await this.activeSlide.getAttribute('data-index'); + return currentIndex; + } + + /** + * Get the count of slides of carousel. + * @return {Promise}. + */ + async getNumberOfSlides() { + const numberOfSlides = await this.slidesCount.count(); + return numberOfSlides; + } + + /** + * Move to next slide . + */ + async moveToNextSlide() { + await this.nextButton.click(); + } + + /** + * Move to previous slide . + */ + async moveToPreviousSlide() { + await this.previousButton.click(); + } + + /** + * Move to nth slide . + */ + async moveToSlide(index) { + await this.indicator.nth(index).click(); + } + + /** + * Are carousel indictors are displayed. + * @return {Promise}. + */ + async areIndicatorsDisplayed() { + const isDisplayed = await this.indicator.isVisible(); + return isDisplayed; + } + + /** + * Get the active indictor index. + * @return {Promise}. + */ + async getCurrentIndicatorIndex() { + const currentIndex = await this.activeIndicator.getAttribute('tabindex'); + return currentIndex; + } + + /** + * Get the count of indicators of carousel. + * @return {Promise}. + */ + async getNumberOfIndicators() { + const numberOfIndicators = await this.indicatorCount.count(); + return numberOfIndicators; + } + + /** + * Move to nth slide by clicking nth indicator + */ + async moveToIndicator(index) { + await this.indicatorCount.nth(index).click(); + } + + /** + * Check carousel button is visible + * @return {Promise}. + */ + async isNextButtonlVisible() { + const isDisplayed = await this.nextButton.isVisible(); + return isDisplayed; + } + + /** + * Check carousel button is visible + * @return {Promise}. + */ + async isPreviousButtonlVisible() { + const isDisplayed = await this.previousButton.isVisible(); + return isDisplayed; + } + + /** + * Check carousel button is visible + * @return {Promise}. + */ + async isLightboxExpandButtonVisible() { + const isDisplayed = await this.lightboxExpandButton.isVisible(); + return isDisplayed; + } + + /** + * Check carousel button is visible + * @return {Promise}. + */ + async isLightboxCloseButtonVisible() { + const isDisplayed = await this.lightboxCloseButton.isVisible(); + return isDisplayed; + } + + /** + * Click carousel button + */ + async expandLightboxModal() { + await this.lightboxExpandButton.click(); + } + + /** + * Click carousel button + */ + async closeLightboxModal() { + await this.lightboxCloseButton.click(); + } + + /** + * Gets the text content of a specified carousel slide. + * @param {number} index - The index of the carousel slide to get the text from. + * @param {string} tagOrClass - The tag name or class name of the element containing the text. + * @returns {Promise} The text content of the specified carousel slide. + */ + async getSlideText(index, tagOrClass) { + let slideSelector = `.carousel-slide:nth-child(${index}) `; + if (tagOrClass.startsWith('.')) { + slideSelector += tagOrClass; + } else { + slideSelector += `${tagOrClass}`; + } + await this.page.waitForSelector(slideSelector); + const slide = await this.page.$(slideSelector); + const text = await slide.textContent(); + return text; + } + + /** + * Gets the text content of a specified carousel slide. + * @param {number} index - The index of the carousel slide to get the text from. + * @param {string} tagOrClass - The tag name or class name of the element containing the text. + * @param {string} expectedText - The text to be validated. + * @returns {Promise} . + */ + async validateSlideText(index, tagOrClass, expectedText) { + let slideSelector = `.carousel-slide:nth-child(${index}) `; + if (tagOrClass.startsWith('.')) { + slideSelector += tagOrClass; + } else { + slideSelector += `${tagOrClass}`; + } + await this.page.waitForSelector(slideSelector); + const slide = await this.page.$(slideSelector); + const slideText = await slide.textContent(); + if (slideText === expectedText) { + return true; + } + return false; + } + + /** + * Check if the specified carousel type is displayed on the page. + * @param {string} type - The type of carousel to check. + * @return {Promise} Returns a Promise that resolves to true or false. + * @throws {Error} Throws an error if an invalid carousel type is provided. + */ + async isCarouselDisplayed(type, timeout = 3000) { + let isDisplayed; + switch (type) { + case 'carouselLightbox': + await this.carouselLightbox.waitFor({ state: 'visible', timeout }); + isDisplayed = await this.carouselLightbox.isVisible(); + break; + case 'carouselFullpage': + await this.carouselFullpage.waitFor({ state: 'visible', timeout }); + isDisplayed = await this.carouselFullpage.isVisible(); + break; + case 'carouselContainer': + await this.carouselContainer.waitFor({ state: 'visible', timeout }); + isDisplayed = await this.carouselContainer.isVisible(); + break; + case 'carouselShow-2': + await this.carouselContainerShow2.waitFor({ state: 'visible', timeout }); + isDisplayed = await this.carouselContainerShow2.isVisible(); + break; + case 'carousel': + await this.carouselDefault.waitFor({ state: 'visible', timeout }); + isDisplayed = await this.carouselDefault.isVisible(); + break; + default: + throw new Error(`Invalid carousel type: ${type}`); + } + return isDisplayed; + } +} diff --git a/nala/blocks/carousel/carousel.spec.js b/nala/blocks/carousel/carousel.spec.js new file mode 100644 index 0000000000..ffc52f7de7 --- /dev/null +++ b/nala/blocks/carousel/carousel.spec.js @@ -0,0 +1,26 @@ +module.exports = { + BlockName: 'Carousel Block', + features: [ + { + tcid: '0', + name: '@Carousel(container)', + path: '/drafts/nala/blocks/carousel/lightbox', + tags: '@carousel @carousel-container @smoke @regression @milo', + envs: '@milo-live @milo-prod', + }, + { + tcid: '1', + name: '@Carousel(lightbox)', + path: '/drafts/nala/blocks/carousel/fullpage-carousel', + tags: '@carousel @carousel-container @smoke @regression @milo', + envs: '@milo-live milo-prod', + }, + { + tcid: '2', + name: '@Carousel Multi slide(show-2)', + path: '/drafts/nala/blocks/carousel/carousel-show-2', + tags: '@carousel @carousel-container @regression @milo', + envs: '@milo-live milo-prod', + }, + ], +}; diff --git a/nala/blocks/carousel/carousel.test.js b/nala/blocks/carousel/carousel.test.js new file mode 100644 index 0000000000..938b0e1131 --- /dev/null +++ b/nala/blocks/carousel/carousel.test.js @@ -0,0 +1,116 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import { features } from './carousel.spec.js'; +import CarouselBlock from './carousel.page.js'; + +let carousel; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Carousel Block test suite', () => { + test.beforeEach(async ({ page }) => { + carousel = new CarouselBlock(page); + }); + + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + + await test.step('step-1: Go to Carousel block test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('networkidle'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Carousel container', async () => { + // verify carousel elements + expect(await carousel.isCarouselDisplayed('carouselContainer')).toBeTruthy(); + + // verify carousel slides count and active slide index + expect(await carousel.getNumberOfSlides()).toBe(4); + expect(await carousel.getCurrentSlideIndex()).toBe('0'); + + // verify carousel indictor and active indicator + expect(await carousel.areIndicatorsDisplayed()).toBeTruthy(); + expect(await carousel.getNumberOfIndicators()).toBe(4); + expect(await carousel.getCurrentIndicatorIndex()).toBe('0'); + + // verify carousel next and previous buttons + expect(await carousel.isNextButtonlVisible()).toBeTruthy(); + expect(await carousel.isPreviousButtonlVisible()).toBeTruthy(); + }); + + await test.step('step-3: Perform carousel slides and controls operation and verify contents', async () => { + // move to next slide by clicking next button and verify h2 tag header + await carousel.moveToNextSlide(); + expect(await carousel.getCurrentSlideIndex()).toBe('1'); + expect(await carousel.getSlideText(1, 'h2', 'Orange Slices')).toBeTruthy(); + + // move to 3rd slide by clicking indicator and verify h2 tag header + await carousel.moveToIndicator(3); + expect(await carousel.getCurrentIndicatorIndex()).toBe('0'); + expect(await carousel.getSlideText(3, 'h2', 'Apples')).toBeTruthy(); + }); + }); + + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + + await test.step('step-1: Go to Carousel lightbox block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('networkidle'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify carousel with lightbox features', async () => { + expect(await carousel.isCarouselDisplayed('carouselLightbox')).toBeTruthy(); + + // verify active slide and slides count + expect(await carousel.getNumberOfSlides()).toBe(4); + expect(await carousel.getCurrentSlideIndex()).toBe('0'); + + // verify indicator visibility, count and index of active slide + expect(await carousel.areIndicatorsDisplayed()).toBeTruthy(); + expect(await carousel.getNumberOfIndicators()).toBe(4); + expect(await carousel.getCurrentIndicatorIndex()).toBe('0'); + + expect(await carousel.isNextButtonlVisible()).toBeTruthy(); + expect(await carousel.isPreviousButtonlVisible()).toBeTruthy(); + + // verify expand and close lightbox + expect(await carousel.isLightboxExpandButtonVisible()).toBeTruthy(); + await carousel.expandLightboxModal(); + + expect(await carousel.isLightboxCloseButtonVisible()).toBeTruthy(); + await carousel.closeLightboxModal(); + }); + }); + + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + + await test.step('step-1: Go to Carousel multi-slide show-2 block test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('networkidle'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify multi slide carousel show-2 features', async () => { + expect(await carousel.isCarouselDisplayed('carouselShow-2')).toBeTruthy(); + + // In multi-slide 2 number of slides will be n-slides +1 so it will be 5 + expect(await carousel.getNumberOfSlides()).toBe(5); + expect(await carousel.getCurrentSlideIndex()).toBe('0'); + + // In multi-slide carousel indicators are not shown + expect(await carousel.areIndicatorsDisplayed()).toBeFalsy(); + expect(await carousel.isNextButtonlVisible()).toBeTruthy(); + expect(await carousel.isPreviousButtonlVisible()).toBeTruthy(); + }); + + await test.step('step-3: Perform carousel slides and controls operation and verify contents', async () => { + // move to next slide by clicking next button and verify h2 tag header + await carousel.moveToNextSlide(); + expect(await carousel.getSlideText(1, 'h2', 'Melon')).toBeTruthy(); + }); + }); +}); diff --git a/nala/blocks/chart/chart.page.js b/nala/blocks/chart/chart.page.js new file mode 100644 index 0000000000..91576c07ce --- /dev/null +++ b/nala/blocks/chart/chart.page.js @@ -0,0 +1,49 @@ +export default class Chart { + constructor(page, nth = 0) { + this.page = page; + // chart locators + this.chart = this.page.locator('.chart').nth(nth); + this.type = this.page.locator('.chart').nth(nth); + + this.title = this.chart.locator('.title'); + this.subTitle = this.chart.locator('.subtitle').nth(0); + this.container = this.chart.locator('.chart-container'); + this.svgImg = this.container.locator('svg'); + this.svgImgCircle = this.container.locator('circle'); + this.svgImgCircleNumber = this.container.locator('text.number'); + this.svgImgCircleSubTitle = this.container.locator('text.subtitle'); + + this.legendChrome = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Chrome' }); + this.legendFirefox = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Firefox' }); + this.legendEdge = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Edge' }); + this.legendSafari = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Safari' }); + this.legendOpera = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Opera' }); + this.legendChromeAndroid = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Chrome Android' }); + this.legendFirefoxAndroid = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Firefox Android' }); + this.legendSafariIos = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Safari iOS' }); + this.legendOperaAndroid = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Opera Android' }); + this.legendSamsungInternet = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Samsung Internet' }); + this.legendAdobeAcrobat = page.locator('text[dominant-baseline="central"][fill="#333"]').filter({ hasText: 'Adobe Acrobat' }); + this.legendAdobeExperienceManager = page.locator('text[dominant-baseline="central"][fill="#333"]').filter({ hasText: 'Adobe Experience Manager' }); + + this.x_axisMonday = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Monday' }); + this.x_axisTuesday = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Tuesday' }); + this.x_axisSunday = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Sunday' }); + this.y_axis50K = page.locator('text[dominant-baseline="central"]').filter({ hasText: '50K', exact: true }); + this.y_axis100K = page.locator('text[dominant-baseline="central"]').filter({ hasText: '100K', exact: true }); + this.y_axis250K = page.locator('text[dominant-baseline="central"]').filter({ hasText: '250K', exact: true }); + this.y_axis300K = page.locator('text[dominant-baseline="central"]').filter({ hasText: '300K', exact: true }); + + this.donutTitle = page.locator('text[dominant-baseline="central"]').filter({ hasText: 'Hello World', exact: true }); + + this.pieChartLabelAdobeSign = page.locator('text[dominant-baseline="central"][text-anchor="end"]').filter({ hasText: 'Adobe Sign' }); + this.pieChartLabelAdobePhotoshop = page.locator('text[dominant-baseline="central"][text-anchor="end"]').filter({ hasText: 'Adobe Photoshop' }); + this.pieChartLabelAdobePremier = page.locator('text[dominant-baseline="central"][text-anchor="end"]').filter({ hasText: 'Adobe Premier' }); + + // chart footer + this.footNote = this.chart.locator('.footnote'); + + // chart attributes + this.attributes = { svgViewBox: { viewBox: '0 0 430 430' } }; + } +} diff --git a/nala/blocks/chart/chart.spec.js b/nala/blocks/chart/chart.spec.js new file mode 100644 index 0000000000..b8ee9b1e52 --- /dev/null +++ b/nala/blocks/chart/chart.spec.js @@ -0,0 +1,91 @@ +module.exports = { + FeatureName: 'Chart Block', + features: [ + { + tcid: '0', + name: '@Chart (area, green, border)', + path: '/drafts/nala/blocks/chart/chart-area-green-border', + data: { + chartType: 'area green border', + titleH3: 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + subTitle: 'Revenue dollars: 2020 vs 2021 forecasted vs 2021 actuals.', + footNote: 'Footnote lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + }, + tags: '@chart @smoke @regression @milo', + }, + { + tcid: '1', + name: '@Chart (bar, border)', + path: '/drafts/nala/blocks/chart/chart-bar-border', + data: { + chartType: 'bar border large', + titleH3: 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + subTitle: 'Revenue dollars: 2020 vs 2021 forecasted vs 2021 actuals.', + footNote: 'Footnote lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + }, + tags: '@chart @smoke @regression @milo', + }, + { + tcid: '2', + name: '@Chart (column, border)', + path: '/drafts/nala/blocks/chart/chart-column-border', + data: { + chartType: 'column border large', + titleH3: 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + subTitle: 'Revenue dollars: 2020 vs 2021 forecasted vs 2021 actuals.', + footNote: 'Footnote lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + }, + tags: '@chart @smoke @regression @milo', + }, + { + tcid: '3', + name: '@Chart (donut, border)', + path: '/drafts/nala/blocks/chart/chart-donut-border', + data: { + chartType: 'donut border large', + titleH3: 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + subTitle: 'Revenue dollars: 2020 vs 2021 forecasted vs 2021 actuals.', + footNote: 'Footnote lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + }, + tags: '@chart @smoke @regression @milo', + }, + { + tcid: '4', + name: '@Chart (line, border)', + path: '/drafts/nala/blocks/chart/chart-line-border', + data: { + chartType: 'line border large', + titleH3: 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + subTitle: 'Revenue dollars: 2020 vs 2021 forecasted vs 2021 actuals.', + footNote: 'Footnote lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + }, + tags: '@chart @smoke @regression @milo', + }, + { + tcid: '5', + name: '@Chart (oversized-number, border)', + path: '/drafts/nala/blocks/chart/chart-oversized-number-border', + data: { + chartType: 'oversized-number border large', + titleH3: 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + subTitle: 'Revenue dollars: 2020 vs 2021 forecasted vs 2021 actuals.', + circleNumber: '25', + circleSubTitle: 'Out of 60 days', + footNote: 'Footnote lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + }, + tags: '@chart @smoke @regression @milo', + }, + { + tcid: '6', + name: '@Chart (pie, border)', + path: '/drafts/nala/blocks/chart/chart-pie-border', + data: { + chartType: 'pie border large', + titleH3: 'Lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + subTitle: 'Revenue dollars: 2020 vs 2021 forecasted vs 2021 actuals.', + footNote: 'Footnote lorem ipsum dolor sit amet, consectetuer adipiscing elit.', + }, + tags: '@chart @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/chart/chart.test.js b/nala/blocks/chart/chart.test.js new file mode 100644 index 0000000000..ecb1ccb505 --- /dev/null +++ b/nala/blocks/chart/chart.test.js @@ -0,0 +1,198 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import { features } from './chart.spec.js'; +import ChartBlock from './chart.page.js'; + +let chart; +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Chart feature test suite', () => { + test.beforeEach(async ({ page }) => { + chart = new ChartBlock(page); + }); + + // Test 0 : Chart (area, green, border) + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + const { data } = features[0]; + + await test.step('step-1: Go to Chart feature test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify chart content/specs', async () => { + await expect(await chart.chart).toBeVisible(); + await expect(await chart.title).toContainText(data.titleH3); + await expect(await chart.subTitle).toContainText(data.subTitle); + expect(await chart.type.getAttribute('class')).toContain(data.chartType); + await expect(await chart.footNote).toContainText(data.footNote); + }); + }); + + // Test 1 : Chart (bar, border) + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + const { data } = features[1]; + + await test.step('step-1: Go to Chart feature test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify chart content/specs', async () => { + await expect(await chart.chart).toBeVisible(); + await expect(await chart.title).toContainText(data.titleH3); + await expect(await chart.subTitle).toContainText(data.subTitle); + expect(await chart.type.getAttribute('class')).toContain(data.chartType); + await expect(await chart.legendChrome).toBeVisible(); + await expect(await chart.legendFirefox).toBeVisible(); + await expect(await chart.legendEdge).toBeVisible(); + + await expect(await chart.footNote).toContainText(data.footNote); + }); + }); + + // Test 2 : Chart (column, border) + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + const { data } = features[2]; + + await test.step('step-1: Go to Chart feature test page', async () => { + await page.goto(`${baseURL}${features[2].path}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify chart content/specs', async () => { + await expect(await chart.chart).toBeVisible(); + await expect(await chart.title).toContainText(data.titleH3); + await expect(await chart.subTitle).toContainText(data.subTitle); + expect(await chart.type.getAttribute('class')).toContain(data.chartType); + + await expect(await chart.x_axisMonday).toBeVisible(); + await expect(await chart.x_axisTuesday).toBeVisible(); + await expect(await chart.x_axisSunday).toBeVisible(); + + await expect(await chart.y_axis100K).toBeVisible(); + await expect(await chart.y_axis250K).toBeVisible(); + await expect(await chart.y_axis300K).toBeVisible(); + + await expect(await chart.legendChrome).toBeVisible(); + await expect(await chart.legendFirefox).toBeVisible(); + await expect(await chart.legendSafari).toBeVisible(); + + await expect(await chart.footNote).toContainText(data.footNote); + }); + }); + + // Test 3 : Chart (donut, border) + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[3].path}${miloLibs}`); + const { data } = features[3]; + + await test.step('step-1: Go to Chart feature test page', async () => { + await page.goto(`${baseURL}${features[3].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[3].path}${miloLibs}`); + }); + + await test.step('step-2: Verify chart content/specs', async () => { + await expect(await chart.chart).toBeVisible(); + await expect(await chart.title).toContainText(data.titleH3); + await expect(await chart.subTitle).toContainText(data.subTitle); + expect(await chart.type.getAttribute('class')).toContain(data.chartType); + + await expect(await chart.donutTitle).toBeVisible(); + await expect(await chart.x_axisMonday).toBeVisible(); + await expect(await chart.x_axisTuesday).toBeVisible(); + await expect(await chart.x_axisSunday).toBeVisible(); + + await expect(await chart.footNote).toContainText(data.footNote); + }); + }); + + // Test 4 : Chart (line, border) + test(`${features[4].name},${features[4].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[4].path}${miloLibs}`); + const { data } = features[4]; + + await test.step('step-1: Go to Chart feature test page', async () => { + await page.goto(`${baseURL}${features[4].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[4].path}${miloLibs}`); + }); + + await test.step('step-2: Verify chart content/specs', async () => { + await expect(await chart.chart).toBeVisible(); + await expect(await chart.title).toContainText(data.titleH3); + await expect(await chart.subTitle).toContainText(data.subTitle); + expect(await chart.type.getAttribute('class')).toContain(data.chartType); + + await expect(await chart.x_axisMonday).toBeVisible(); + await expect(await chart.x_axisTuesday).toBeVisible(); + await expect(await chart.x_axisSunday).toBeVisible(); + + await expect(await chart.legendChromeAndroid).toBeVisible(); + await expect(await chart.legendFirefoxAndroid).toBeVisible(); + await expect(await chart.legendSafariIos).toBeVisible(); + + await expect(await chart.footNote).toContainText(data.footNote); + }); + }); + + // Test 5 : Chart (oversized-number, border) + test(`${features[5].name},${features[5].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[5].path}${miloLibs}`); + const { data } = features[5]; + + await test.step('step-1: Go to Chart feature test page', async () => { + await page.goto(`${baseURL}${features[5].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[5].path}${miloLibs}`); + }); + + await test.step('step-2: Verify chart content/specs', async () => { + await expect(await chart.chart).toBeVisible(); + await expect(await chart.title).toContainText(data.titleH3); + await expect(await chart.subTitle).toContainText(data.subTitle); + + expect(await chart.type.getAttribute('class')).toContain(data.chartType); + expect(await chart.svgImg.getAttribute('viewBox')).toContain(chart.attributes.svgViewBox.viewBox); + + await expect(await chart.svgImgCircleNumber).toContainText(data.circleNumber); + await expect(await chart.svgImgCircleSubTitle).toContainText(data.circleSubTitle); + + await expect(await chart.footNote).toContainText(data.footNote); + }); + }); + + // Test 6 : Chart (pie, border) + test(`${features[6].name},${features[6].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[6].path}${miloLibs}`); + const { data } = features[6]; + + await test.step('step-1: Go to Chart feature test page', async () => { + await page.goto(`${baseURL}${features[6].path}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[6].path}${miloLibs}`); + }); + + await test.step('step-2: Verify chart content/specs', async () => { + await expect(await chart.chart).toBeVisible(); + await expect(await chart.title).toContainText(data.titleH3); + await expect(await chart.subTitle).toContainText(data.subTitle); + + await expect(await chart.pieChartLabelAdobeSign).toBeVisible(); + await expect(await chart.pieChartLabelAdobePhotoshop).toBeVisible(); + await expect(await chart.pieChartLabelAdobePremier).toBeVisible(); + + await expect(await chart.legendAdobeAcrobat).toBeVisible(); + await expect(await chart.legendAdobeExperienceManager).toBeVisible(); + + await expect(await chart.footNote).toContainText(data.footNote); + }); + }); +}); diff --git a/nala/blocks/columns/columns.page.js b/nala/blocks/columns/columns.page.js new file mode 100644 index 0000000000..873a75982c --- /dev/null +++ b/nala/blocks/columns/columns.page.js @@ -0,0 +1,52 @@ +export default class Columns { + constructor(page, nth = 0) { + this.page = page; + // columns locators + this.column = this.page.locator('.columns').nth(nth); + this.rows = this.column.locator('.row'); + this.columns = this.column.locator('.col'); + + // columns blocks css + this.cssProperties = { + '.columns > .row': { + display: 'grid', + gap: /^32px.*/, + 'margin-bottom': '16px', + 'grid-template-columns': /^(\d+(?:\.\d+)?px\s?)+$/, + }, + + '.columns.contained': { + 'max-width': /^\d{2}/, + margin: /^0px.*/, + }, + + '.columns.contained.middle': { 'align-items': 'center' }, + + '.columns.table': { 'font-size': '14px' }, + + '.columns.table > .row:first-child': { + 'text-transform': 'uppercase', + 'font-size': '11px', + 'font-weight': '700', + 'letter-spacing': '1px', + }, + + '.columns.table > .row': { + 'margin-bottom': '0px', + padding: /^16px.*/, + 'grid-template-columns': /^(\d+(?:\.\d+)?px\s?)+$/, + 'border-bottom': /^1px.*/, + 'align-items': 'center', + }, + }; + + // columns blocks attributes + this.attProperties = { + columns: { class: 'columns' }, + 'columns-contained': { class: 'columns contained' }, + 'columns-contained-middle': { class: 'columns contained middle' }, + 'columns-table': { class: 'columns columns-table' }, + 'columns-contained-table': { class: 'columns contained columns-table' }, + }; + } +} diff --git a/nala/blocks/columns/columns.spec.js b/nala/blocks/columns/columns.spec.js new file mode 100644 index 0000000000..14a089b449 --- /dev/null +++ b/nala/blocks/columns/columns.spec.js @@ -0,0 +1,70 @@ +module.exports = { + FeatureName: 'Columns Block', + features: [ + { + tcid: '0', + name: '@Columns', + path: '/drafts/nala/blocks/columns/columns', + data: { + rows: 1, + columns: 3, + col0: 'Other glossary terms', + col1: 'Related Adobe products', + col2: 'Adobe Target', + }, + tags: '@columns @smoke @regression @milo', + }, + { + tcid: '1', + name: '@Columns (contained)', + path: '/drafts/nala/blocks/columns/columns-contained', + data: { + rows: 1, + columns: 2, + col0: 'Market Segmentation', + col1: 'Adobe Analytics', + }, + tags: '@columns @columns-contained @smoke @regression @milo', + }, + { + tcid: '2', + name: '@Columns (contained,middle)', + path: '/drafts/nala/blocks/columns/columns-contained-middle', + data: { + rows: 1, + columns: 2, + col0: 'Descriptive Analytics', + col1: 'Adobe Target', + }, + tags: '@columns @columns-contained-middle @smoke @regression @milo', + }, + { + tcid: '3', + name: '@Columns (table)', + path: '/drafts/nala/blocks/columns/columns-table', + data: { + rows: 4, + columns: 8, + col0: 'PROS', + col1: 'CONS', + col2: 'Detail: Waterfall’s meticulous upfront planning results in detailed project plans.', + col3: 'Rigid: With a strict blueprint, departure from the original plan is difficult.', + }, + tags: '@columns @columns-table @smoke @regression @milo', + }, + { + tcid: '4', + name: '@Columns (contained,table)', + path: '/drafts/nala/blocks/columns/columns-contained-table', + data: { + rows: 10, + columns: 20, + col0: 'Role', + col1: 'Name', + col2: 'Engineering Manager', + col3: 'Chris Millar', + }, + tags: '@columns @columns-contained-table @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/columns/columns.test.js b/nala/blocks/columns/columns.test.js new file mode 100644 index 0000000000..3053c5325a --- /dev/null +++ b/nala/blocks/columns/columns.test.js @@ -0,0 +1,161 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import WebUtil from '../../libs/webutil.js'; +import { features } from './columns.spec.js'; +import ColumnsBlock from './columns.page.js'; + +let column; +let webUtil; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Columns Block test suite', () => { + test.beforeEach(async ({ page }) => { + column = new ColumnsBlock(page); + webUtil = new WebUtil(page); + }); + + // Test 0 : Column default block + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + const { data } = features[0]; + + await test.step('step-1: Go to Columns block test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Columns block content/specs', async () => { + // verify colums visibility, rows count, columns count and text content + await expect(await column.column).toBeVisible(); + + await expect(await column.rows).toHaveCount(data.rows); + await expect(await column.columns).toHaveCount(data.columns); + + await expect(await column.columns.nth(0)).toContainText(data.col0); + await expect(await column.columns.nth(1)).toContainText(data.col1); + await expect(await column.columns.nth(2)).toContainText(data.col2); + + // verify the css and attributes + expect(await webUtil.verifyCSS(await column.rows.nth(0), column.cssProperties['.columns > .row'])).toBeTruthy(); + expect(await webUtil.verifyAttributes(await column.column, column.attProperties.columns)).toBeTruthy(); + }); + }); + + // Test 1 : Columns (contained) block + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + const { data } = features[1]; + + await test.step('step-1: Go to Columns block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Columns(contained) block content/specs', async () => { + // verify colums visibility, rows count, columns count and text content + await expect(await column.column).toBeVisible(); + + await expect(await column.rows).toHaveCount(data.rows); + await expect(await column.columns).toHaveCount(data.columns); + + await expect(await column.columns.nth(0)).toContainText(data.col0); + await expect(await column.columns.nth(1)).toContainText(data.col1); + + // verify the css and attributes + expect(await webUtil.verifyCSS(await column.column, column.cssProperties['.columns.contained'])).toBeTruthy(); + expect(await webUtil.verifyAttributes(await column.column, column.attProperties['columns-contained'])).toBeTruthy(); + }); + }); + + // Test 2 : Columns (contained,middle) block + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + const { data } = features[2]; + + await test.step('step-1: Go to Columns block test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Columns(contained,middle) block content/specs', async () => { + // verify colums visibility, rows count, columns count and text content + await expect(await column.column).toBeVisible(); + + await expect(await column.rows).toHaveCount(data.rows); + await expect(await column.columns).toHaveCount(data.columns); + + await expect(await column.columns.nth(0)).toContainText(data.col0); + await expect(await column.columns.nth(1)).toContainText(data.col1); + + // verify the css and attributes + expect(await webUtil.verifyCSS(await column.column, column.cssProperties['.columns.contained'])).toBeTruthy(); + expect(await webUtil.verifyAttributes(await column.column, column.attProperties['columns-contained-middle'])).toBeTruthy(); + }); + }); + + // Test 3 : Columns (table) block + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[3].path}${miloLibs}`); + const { data } = features[3]; + + await test.step('step-1: Go to Columns block test page', async () => { + await page.goto(`${baseURL}${features[3].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[3].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Columns(table) block content/specs', async () => { + // verify colums visibility, rows count, columns count and text content + await expect(await column.column).toBeVisible(); + + await expect(await column.rows).toHaveCount(data.rows); + await expect(await column.columns).toHaveCount(data.columns); + + await expect(await column.columns.nth(0)).toContainText(data.col0); + await expect(await column.columns.nth(1)).toContainText(data.col1); + await expect(await column.columns.nth(2)).toContainText(data.col2); + await expect(await column.columns.nth(3)).toContainText(data.col3); + + // verify the css and attributes + expect(await webUtil.verifyCSS(await column.rows.nth(0), column.cssProperties['.columns.table > .row:first-child'])).toBeTruthy(); + expect(await webUtil.verifyCSS(await column.rows.nth(1), column.cssProperties['.columns.table > .row'])).toBeTruthy(); + + expect(await webUtil.verifyAttributes(await column.column, column.attProperties['columns-table'])).toBeTruthy(); + }); + }); + + // Test 4 : Columns (contained,table) block + test(`${features[4].name},${features[4].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[4].path}${miloLibs}`); + const { data } = features[4]; + + await test.step('step-1: Go to Columns block test page', async () => { + await page.goto(`${baseURL}${features[4].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[4].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Columns(contained,table) block content/specs', async () => { + // verify colums visibility, rows count, columns count and text content + await expect(await column.column).toBeVisible(); + + await expect(await column.rows).toHaveCount(data.rows); + await expect(await column.columns).toHaveCount(data.columns); + + await expect(await column.columns.nth(0)).toContainText(data.col0); + await expect(await column.columns.nth(1)).toContainText(data.col1); + await expect(await column.columns.nth(2)).toContainText(data.col2); + await expect(await column.columns.nth(3)).toContainText(data.col3); + + // verify the css and attributes + expect(await webUtil.verifyCSS(await column.rows.nth(0), column.cssProperties['.columns.table > .row:first-child'])).toBeTruthy(); + expect(await webUtil.verifyCSS(await column.rows.nth(1), column.cssProperties['.columns.table > .row'])).toBeTruthy(); + + expect(await webUtil.verifyAttributes(await column.column, column.attProperties['columns-contained-table'])).toBeTruthy(); + }); + }); +}); diff --git a/nala/blocks/figure/figure.page.js b/nala/blocks/figure/figure.page.js new file mode 100644 index 0000000000..785efa832d --- /dev/null +++ b/nala/blocks/figure/figure.page.js @@ -0,0 +1,9 @@ +export default class Figure { + constructor(page) { + this.page = page; + // figure block locators + this.figure = this.page.locator('figure.figure'); + this.image = this.figure.locator('picture img'); + this.figCaption = this.figure.locator('figcaption .caption'); + } +} diff --git a/nala/blocks/figure/figure.spec.js b/nala/blocks/figure/figure.spec.js new file mode 100644 index 0000000000..84e8b37193 --- /dev/null +++ b/nala/blocks/figure/figure.spec.js @@ -0,0 +1,23 @@ +module.exports = { + FeatureName: 'Figure Block', + features: [ + { + tcid: '0', + name: '@Image with caption', + path: '/drafts/nala/blocks/figure/image-with-caption', + data: { figCaption: '100 Orange Captions' }, + tags: '@figure @smoke @regression @milo', + }, + { + tcid: '01', + name: '@Multiple images with caption', + path: '/drafts/nala/blocks/figure/multiple-images-with-captions', + data: { + figBlockCount: 2, + figCaption_1: 'Adobe Logo', + figCaption_2: 'Adobe Products', + }, + tags: '@figure @figure-with-mulitple-images @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/figure/figure.test.js b/nala/blocks/figure/figure.test.js new file mode 100644 index 0000000000..1320929b5c --- /dev/null +++ b/nala/blocks/figure/figure.test.js @@ -0,0 +1,52 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import { features } from './figure.spec.js'; +import FigureBlock from './figure.page.js'; + +let figureBlock; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Figure Block test suite', () => { + test.beforeEach(async ({ page }) => { + figureBlock = new FigureBlock(page); + }); + + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + + await test.step('step-1: Go to figure Block test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify figure block ', async () => { + const { data } = features[0]; + await expect(await figureBlock.figure).toBeVisible(); + await expect(await figureBlock.image).toBeVisible(); + await expect(await figureBlock.figCaption).toContainText(data.figCaption); + }); + }); + + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + + await test.step('step-1: Go to figure block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify figure block multiple images with caption ', async () => { + const { data } = features[1]; + await expect(await figureBlock.figure).toHaveCount(data.figBlockCount); + + await expect(await figureBlock.image.nth(0)).toBeVisible(); + await expect(await figureBlock.figCaption.nth(0)).toContainText(data.figCaption_1); + + await expect(await figureBlock.image.nth(1)).toBeVisible(); + await expect(await figureBlock.figCaption.nth(1)).toContainText(data.figCaption_2); + }); + }); +}); diff --git a/nala/blocks/howto/howto.page.js b/nala/blocks/howto/howto.page.js new file mode 100644 index 0000000000..af06eafeee --- /dev/null +++ b/nala/blocks/howto/howto.page.js @@ -0,0 +1,52 @@ +export default class HowTo { + constructor(page, nth = 0) { + this.page = page; + // how-to locators + this.howTo = page.locator('.how-to').nth(nth); + this.foreground = this.howTo.locator('.foreground'); + this.howToLarge = this.page.locator('.how-to.large-image').nth(nth); + this.howToSeo = this.page.locator('.how-to.seo').nth(nth); + this.heading = this.howTo.locator('.how-to-heading'); + this.image = this.howTo.locator('.how-to-media'); + this.list = this.howTo.locator('li'); + this.largeImage = page.locator('.how-to-media img'); + + // howto contents css + this.cssProperties = { + '.how-to .foreground': { + padding: '80px 0px', + 'max-width': /%$/, + display: 'grid', + }, + 'how-to-media': { + 'align-self': 'center', + 'justify-self': 'center', + 'min-height': '100%', + }, + 'body-m': { + 'font-size': '18px', + 'line-height': '27px', + }, + 'how-to-large': { + padding: '80px 24px', + 'max-width': '700px', + }, + 'how-to-large-image': { + display: 'block', + 'grid-template-areas': 'none', + }, + 'how-to-seo': { + display: 'block', + 'grid-template-areas': 'none', + }, + }; + + // howto contents attributes + this.attProperties = { + 'how-to-large-image': { + width: '600', + height: '300', + }, + }; + } +} diff --git a/nala/blocks/howto/howto.spec.js b/nala/blocks/howto/howto.spec.js new file mode 100644 index 0000000000..e62227e90c --- /dev/null +++ b/nala/blocks/howto/howto.spec.js @@ -0,0 +1,23 @@ +module.exports = { + BlockName: 'HowTo Block', + features: [ + { + tcid: '1', + name: '@HowTo', + path: '/drafts/nala/blocks/howto/how-to', + tags: '@howto @smoke @regression @milo', + }, + { + tcid: '2', + name: '@HowTo large Image', + path: '/drafts/nala/blocks/howto/how-to-large', + tags: '@howto @howto-large-image @smoke @regression @milo', + }, + { + tcid: '3', + name: '@HowTo SEO', + path: '/drafts/nala/blocks/howto/how-to-seo', + tags: '@howto @howto-seo @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/howto/howto.test.js b/nala/blocks/howto/howto.test.js new file mode 100644 index 0000000000..afbbf5eb89 --- /dev/null +++ b/nala/blocks/howto/howto.test.js @@ -0,0 +1,77 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import WebUtil from '../../libs/webutil.js'; +import { features } from './howto.spec.js'; +import HowToBlock from './howto.page.js'; + +let webUtil; +let howTo; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo HowTo block test suite', () => { + test.beforeEach(async ({ page }) => { + webUtil = new WebUtil(page); + howTo = new HowToBlock(page); + }); + + // Test 0 : HowTo default block + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + + await test.step('step-1: Go to HowTo block test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify HowTo specs', async () => { + await expect(howTo.howTo).toBeVisible(); + await expect(await howTo.list).toHaveCount(4); + + expect(await webUtil.verifyCSS(howTo.foreground, howTo.cssProperties['.how-to .foreground'])).toBeTruthy(); + expect(await webUtil.verifyCSS(howTo.heading, howTo.cssProperties['body-m'])).toBeTruthy(); + expect(await webUtil.verifyCSS(howTo.image, howTo.cssProperties['how-to-media'])).toBeTruthy(); + }); + }); + + // Test 1 : how-to (large) block + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + + await test.step('step-1: Go to HowTo large block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify HowTo large specs', async () => { + await expect(howTo.howToLarge).toBeVisible(); + await expect(await howTo.list).toHaveCount(4); + + expect(await webUtil.verifyCSS(howTo.heading, howTo.cssProperties['body-m'])).toBeTruthy(); + expect(await webUtil.verifyCSS(howTo.howToLarge, howTo.cssProperties['how-to-large-image'])).toBeTruthy(); + // eslint-disable-next-line max-len + expect(await webUtil.verifyAttributes(await howTo.largeImage, howTo.attProperties['how-to-large-image'])).toBeTruthy(); + }); + }); + + // Test 2 : how-to (seo) block + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + + await test.step('step-1: Go to HowTo SEO block test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify HowTo SEO specs', async () => { + await expect(howTo.howToSeo).toBeVisible(); + await expect(await howTo.list).toHaveCount(4); + + expect(await webUtil.verifyCSS(howTo.heading, howTo.cssProperties['body-m'])).toBeTruthy(); + expect(await webUtil.verifyCSS(howTo.howToSeo, howTo.cssProperties['how-to-seo'])).toBeTruthy(); + }); + }); +}); diff --git a/nala/blocks/icon/icon.page.js b/nala/blocks/icon/icon.page.js new file mode 100644 index 0000000000..bcd170dca9 --- /dev/null +++ b/nala/blocks/icon/icon.page.js @@ -0,0 +1,102 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console, no-await-in-loop, import/extensions */ +import { expect } from '@playwright/test'; +import WebUtil from '../../libs/webutil.js'; + +export default class Icon { + constructor(page) { + this.page = page; + this.webUtil = new WebUtil(page); + // Icon block locators + this.icon = this.page.locator('.icon-block').nth(0); + this.iconImage = this.icon.locator('img'); + this.iconHeadingL = this.icon.locator('.heading-l'); + this.iconHeadingXL = this.icon.locator('.heading-xl'); + this.iconBodyM = this.icon.locator('p.body-m').nth(0); + this.iconActionAreaBodyM = this.icon.locator('p.body-m.action-area').nth(0); + this.iconActionAreaLink = this.icon.locator('.action-area a').nth(0); + this.iconActionArea = this.icon.locator('.action-area'); + this.iconSupplemental = this.icon.locator('.supplemental-text'); + + // icon blocks css + this.cssProperties = { + '.icon-block': { + display: 'block', + width: /^\d+px$/, + position: 'relative', + }, + '.con-block.xl-spacing-static-bottom': { 'padding-bottom': '56px' }, + '.con-block.xl-spacing-static-top': { 'padding-top': '104px' }, + }; + + // icon blocks attributes + this.attProperties = { + icon: { class: 'icon-block' }, + 'icon-fullwidth-medium': { class: 'icon-block full-width medium con-block' }, + 'icon-fullwidth-medium-intro': { + class: + 'icon-block full-width medium intro con-block xxxl-spacing-top xl-spacing-static-bottom', + }, + 'icon-fullwidth-large': { class: 'icon-block full-width large con-block' }, + }; + } + + /** + * Verifies the visibility, css, attributes, styles, of elements or sections of + * the specified Icon block. + * + * @param {string} iconType - The type of the Icon block to verify. + * Possible values are 'icon-block (fullwidth, medium) ', 'icon-block (fullwidth, medium, intro)', and + * 'icon-block (fullwidth, large)'. + * @returns {Promise} - Returns true if the specified Quote type has the expected values. + */ + async verifyIcon(iconType, data) { + // verify icon block and image visibility + await expect(await this.icon).toBeVisible(); + await expect(await this.iconImage).toBeVisible(); + + switch (iconType) { + case 'icon block (fullwidth, medium)': + // verify icon block contents + await expect(await this.iconHeadingL).toContainText(data.headline); + await expect(await this.iconBodyM).toContainText(data.body); + await expect(await this.iconActionAreaBodyM).toContainText(data.buttonText); + await expect(await this.iconSupplemental).toContainText(data.supplementalText); + + // verify icon block attributes and css + expect(await this.webUtil.verifyAttributes(await this.icon, this.attProperties['icon-fullwidth-medium'])).toBeTruthy(); + + expect(await this.webUtil.verifyCSS(await this.icon, this.cssProperties['.icon-block'])).toBeTruthy(); + + return true; + case 'icon block (fullwidth, medium, intro)': + // verify icon block contents + await expect(await this.iconHeadingL).toContainText(data.headline); + await expect(await this.iconBodyM).toContainText(data.body); + await expect(await this.iconActionAreaBodyM).toContainText(data.buttonText); + await expect(await this.iconSupplemental).toContainText(data.supplementalText); + + // verify icon block attributes and css + expect(await this.webUtil.verifyAttributes(await this.icon, this.attProperties['icon-fullwidth-medium-intro'])).toBeTruthy(); + + expect(await this.webUtil.verifyCSS(await this.icon, this.cssProperties['.icon-block'])).toBeTruthy(); + expect(await this.webUtil.verifyCSS(await this.icon, this.cssProperties['.con-block.xl-spacing-static-bottom'])).toBeTruthy(); + expect(await this.webUtil.verifyCSS(await this.icon, this.cssProperties['.con-block.xl-spacing-static-top'])).toBeTruthy(); + + return true; + case 'icon block (fullwidth, large)': + // verify icon block contents + await expect(await this.iconHeadingXL).toContainText(data.headline); + await expect(await this.iconBodyM).toContainText(data.body); + await expect(await this.iconActionAreaLink).toContainText(data.linkText); + + // verify icon block attributes and css + expect(await this.webUtil.verifyAttributes(await this.icon, this.attProperties['icon-fullwidth-large'])).toBeTruthy(); + + expect(await this.webUtil.verifyCSS(await this.icon, this.cssProperties['.icon-block'])).toBeTruthy(); + + return true; + default: + throw new Error(`Unsupported Text type: ${this.iconType}`); + } + } +} diff --git a/nala/blocks/icon/icon.spec.js b/nala/blocks/icon/icon.spec.js new file mode 100644 index 0000000000..407aad1090 --- /dev/null +++ b/nala/blocks/icon/icon.spec.js @@ -0,0 +1,43 @@ +module.exports = { + FeatureName: 'Icon Block', + features: [ + { + tcid: '0', + name: '@Icon block (fullwidth, medium)', + path: '/drafts/nala/blocks/icon/icon-fullwidth-medium', + data: { + image: 'photoshop', + headline: 'Heading L Bold Icon Block', + body: 'Body M Lorem ipsum dolor sit', + buttonText: 'Learn more', + supplementalText: 'Body M BOLD Text link', + }, + tags: '@icon @icon-fullwidth-medium @smoke @regression @milo,', + }, + { + tcid: '1', + name: '@Icon block (fullwidth, medium, intro)', + path: '/drafts/nala/blocks/icon/fullwidth-medium-intro', + data: { + image: 'photoshop', + headline: 'Heading L Bold Icon Block', + body: 'Body M Lorem ipsum dolor sit', + buttonText: 'Learn more', + supplementalText: 'Body M BOLD Text link', + }, + tags: '@icon @icon-fullwidth-medium-intro @smoke @regression @milo,', + }, + { + tcid: '2', + name: '@Icon block (fullwidth,large)', + path: '/drafts/nala/blocks/icon/fullwidth-large', + data: { + image: 'photoshop', + headline: 'Heading XL Bold Icon Block', + body: 'Body M Lorem ipsum dolor sit', + linkText: 'Body M BOLD Text link', + }, + tags: '@icon @icon-fullwidth-large, @smoke @regression @milo,', + }, + ], +}; diff --git a/nala/blocks/icon/icon.test.js b/nala/blocks/icon/icon.test.js new file mode 100644 index 0000000000..7b58d1683c --- /dev/null +++ b/nala/blocks/icon/icon.test.js @@ -0,0 +1,59 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import { features } from './icon.spec.js'; +import IconBlock from './icon.page.js'; + +let icon; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Icon Block test suite', () => { + test.beforeEach(async ({ page }) => { + icon = new IconBlock(page); + }); + + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + + await test.step('step-1: Go to Icon block test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Icon block content/specs', async () => { + const { data } = features[0]; + expect(await icon.verifyIcon('icon block (fullwidth, medium)', data)).toBeTruthy(); + }); + }); + + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + + await test.step('step-1: Go to Icon block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Icon block content/specs', async () => { + const { data } = features[1]; + expect(await icon.verifyIcon('icon block (fullwidth, medium, intro)', data)).toBeTruthy(); + }); + }); + + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + + await test.step('step-1: Go to Icon block test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Icon block content/specs', async () => { + const { data } = features[2]; + expect(await icon.verifyIcon('icon block (fullwidth, large)', data)).toBeTruthy(); + }); + }); +}); diff --git a/nala/blocks/iframe/iframe.page.js b/nala/blocks/iframe/iframe.page.js new file mode 100644 index 0000000000..33dbf3762c --- /dev/null +++ b/nala/blocks/iframe/iframe.page.js @@ -0,0 +1,14 @@ +/* eslint-disable import/no-import-module-exports */ + +export default class Iframe { + constructor(page) { + this.page = page; + + // IFRAME ELEMENTS: + this.miloIframeContainer = page.locator('.milo-iframe'); + this.iframeContainer = this.miloIframeContainer.locator('iframe'); + + // IFRAME PROPS: + this.props = {}; + } +} diff --git a/nala/blocks/iframe/iframe.spec.js b/nala/blocks/iframe/iframe.spec.js new file mode 100644 index 0000000000..d5def97fc5 --- /dev/null +++ b/nala/blocks/iframe/iframe.spec.js @@ -0,0 +1,11 @@ +module.exports = { + name: 'Iframe Block', + features: [ + { + name: '@Iframe', + path: '/drafts/nala/blocks/iframe/iframe', + browserParams: '?georouting=off', + tags: '@iframe @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/iframe/iframe.test.js b/nala/blocks/iframe/iframe.test.js new file mode 100644 index 0000000000..50f0a07071 --- /dev/null +++ b/nala/blocks/iframe/iframe.test.js @@ -0,0 +1,25 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import { features } from './iframe.spec.js'; +import IframeBlock from './iframe.page.js'; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Iframe Block test suite', () => { + // Iframe Block Checks: + test(`${features[0].name}, ${features[0].tags}`, async ({ page, baseURL }) => { + const Iframe = new IframeBlock(page); + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + + await test.step('Navigate to page with Iframe block', async () => { + await page.goto(`${baseURL}${features[0].path}${features[0].browserParams}&${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${features[0].browserParams}&${miloLibs}`); + }); + + await test.step('Validate Iframe block content', async () => { + await expect(Iframe.miloIframeContainer).toBeVisible(); + await expect(Iframe.iframeContainer).toBeVisible(); + }); + }); +}); diff --git a/nala/blocks/marketo/marketo.page.js b/nala/blocks/marketo/marketo.page.js new file mode 100644 index 0000000000..47bfe2533c --- /dev/null +++ b/nala/blocks/marketo/marketo.page.js @@ -0,0 +1,131 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console, no-await-in-loop, import/extensions */ +import { expect } from '@playwright/test'; + +const FIRST_NAME = 'firstNameTest'; +const LAST_NAME = 'lastNameTest'; +const PHONE = '415-123-4567'; +const EMAIL = 'test+nosub@adobetest.com'; +const COMPANY = 'Adobe'; +const POSTAL_CODE = '94111'; + +export default class Marketo { + constructor(page) { + this.page = page; + this.marketo = this.page.locator('.marketo'); + this.firstName = this.marketo.locator('input[name="FirstName"]'); + this.lastName = this.marketo.locator('input[name="LastName"]'); + this.email = this.marketo.locator('input[name="Email"]'); + this.phone = this.marketo.locator('input[name="Phone"]'); + this.company = this.marketo.locator('input[name="mktoFormsCompany"]'); + this.functionalArea = this.marketo.locator( + 'select[name="mktoFormsFunctionalArea"]', + ); + this.country = this.marketo.locator('select[name="Country"]'); + this.state = this.marketo.locator('select[name="State"]'); + this.postalCode = this.marketo.locator('input[name="PostalCode"]'); + this.jobTitle = this.marketo.locator('select[name="mktoFormsJobTitle"]'); + this.primaryProductInterest = this.marketo.locator( + 'select[name="mktoFormsPrimaryProductInterest"]', + ); + this.companyType = this.marketo.locator( + 'select[name="mktoFormsCompanyType"]', + ); + this.submitButton = this.marketo.locator('#mktoButton_new'); + this.message = this.marketo.locator('.ty-message'); + } + + async submitFullTemplateForm(poi) { + await this.country.selectOption({ index: 1 }); + await this.jobTitle.selectOption({ index: 1 }); + await this.company.fill(COMPANY); + await this.firstName.fill(FIRST_NAME); + await this.lastName.fill(LAST_NAME); + await this.email.fill(EMAIL); + await this.phone.fill(PHONE); + await this.functionalArea.selectOption({ index: 1 }); + await this.postalCode.fill(POSTAL_CODE); + + // Setting index 2 to test so that the 'Company Type' field doesn't display + await this.primaryProductInterest.selectOption(poi !== undefined ? poi : { index: 2 }); + + await this.state.selectOption({ index: 1 }); + await this.selectCompanyType(); + await this.submitButton.click(); + } + + async submitExpandedTemplateForm() { + await this.country.selectOption({ index: 1 }); + await this.jobTitle.selectOption({ index: 1 }); + await this.firstName.fill(FIRST_NAME); + await this.lastName.fill(LAST_NAME); + await this.email.fill(EMAIL); + await this.functionalArea.selectOption({ index: 1 }); + await this.company.fill(COMPANY); + await this.selectCompanyType(); + await this.submitButton.click(); + } + + async submitEssentialTemplateForm() { + await this.country.selectOption({ index: 1 }); + await this.firstName.fill(FIRST_NAME); + await this.lastName.fill(LAST_NAME); + await this.email.fill(EMAIL); + await this.company.fill(COMPANY); + await this.selectCompanyType(); + await this.submitButton.click(); + } + + async getPOI() { + const poi = await this.page.evaluate( + 'window.mcz_marketoForm_pref.program.poi', + ); + return poi; + } + + async getFormTemplate() { + const template = await this.page.evaluate( + 'window.mcz_marketoForm_pref.form.template', + ); + return template; + } + + async selectCompanyType() { + // The company type field will display if the poi is one of the below + const expectedPOI = ['Commerce', 'ADOBEADVERTISINGCLOUD']; + + if (expectedPOI.includes(await this.getPOI())) { + this.companyType.selectOption({ index: 1 }); + } + } + + /** + * @description Checks that the input fields have the placeholder attribute + * and that the value isn't empty. + */ + async checkInputPlaceholders() { + const template = await this.page.evaluate( + 'window.mcz_marketoForm_pref.form.template', + ); + + if (!template) { + throw new Error('Template not found'); + } + + const inputFields = [ + this.firstName, + this.lastName, + this.email, + this.company, + ]; + + if (template === 'flex_contact') inputFields.push(this.phone, this.postalCode); + + inputFields.forEach(async (field) => { + await expect(async () => { + expect(await field).toHaveAttribute('placeholder', { timeout: 10000 }); + const placeholder = await field.getAttribute('placeholder'); + expect(placeholder.length).toBeGreaterThan(1); + }).toPass(); + }); + } +} diff --git a/nala/blocks/marketo/marketo.spec.js b/nala/blocks/marketo/marketo.spec.js new file mode 100644 index 0000000000..755f63ac5c --- /dev/null +++ b/nala/blocks/marketo/marketo.spec.js @@ -0,0 +1,73 @@ +module.exports = { + name: 'Marketo Forms block', + features: [ + { + tcid: '0', + name: '@marketo full template', + path: [ + '/drafts/nala/blocks/marketo/full', + ], + tags: '@marketo @marketoFullRedirect @marketoRedirect @milo @smoke @regression', + }, + { + tcid: '1', + name: '@marketo full template with company type', + path: [ + '/drafts/nala/blocks/marketo/full-with-company-type', + ], + tags: '@marketo @marketoFullRedirect @marketoRedirect @milo @smoke @regression', + }, + { + tcid: '2', + name: '@marketo expanded template', + path: [ + '/drafts/nala/blocks/marketo/expanded', + '/drafts/nala/blocks/marketo/expanded-with-company-type', + ], + tags: '@marketo @marketoExpandedRedirect @marketoRedirect @milo @smoke @regression', + }, + { + tcid: '3', + name: '@marketo essential template', + path: [ + '/drafts/nala/blocks/marketo/essential', + '/drafts/nala/blocks/marketo/essential-with-company-type', + ], + tags: '@marketo @marketoEssentialRedirect @marketoRedirect @milo @smoke @regression', + }, + { + tcid: '4', + name: '@marketo full template message', + path: [ + '/drafts/nala/blocks/marketo/full-message', + ], + tags: '@marketo @marketoFullMessage @marketoMessage @milo @smoke @regression', + }, + { + tcid: '5', + name: '@marketo full template with company type', + path: [ + '/drafts/nala/blocks/marketo/full-message-with-company-type', + ], + tags: '@marketo @marketoFullMessage @marketoMessage @milo @smoke @regression', + }, + { + tcid: '6', + name: '@marketo expanded template', + path: [ + '/drafts/nala/blocks/marketo/expanded-message', + '/drafts/nala/blocks/marketo/expanded-message-with-company-type', + ], + tags: '@marketo @marketoExpandedMessage @marketoMessage @milo @smoke @regression', + }, + { + tcid: '7', + name: '@marketo essential template', + path: [ + '/drafts/nala/blocks/marketo/essential-message', + '/drafts/nala/blocks/marketo/essential-message-with-company-type', + ], + tags: '@marketo @marketoEssentialMessage @marketoMessage @milo @smoke @regression', + }, + ], +}; diff --git a/nala/blocks/marketo/marketo.test.js b/nala/blocks/marketo/marketo.test.js new file mode 100644 index 0000000000..7f5a3f09ab --- /dev/null +++ b/nala/blocks/marketo/marketo.test.js @@ -0,0 +1,279 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import { features } from './marketo.spec.js'; +import MarketoBlock from './marketo.page.js'; + +let marketoBlock; +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Marketo block test suite', () => { + test.beforeAll(async ({ browserName }) => { + if (browserName === 'chromium' && process.env.CI) test.skip('TODO: debug why this is failing on github actions'); + + if (process.env.CI) test.setTimeout(1000 * 60 * 3); // 3 minutes + }); + + test.beforeEach(async ({ page }) => { + marketoBlock = new MarketoBlock(page); + }); + + features[0].path.forEach((path) => { + test(`0: @marketo full template (redirect), ${features[0].tags}, path: ${path}`, async ({ + page, + baseURL, + }) => { + const testPage = `${baseURL}${path}${miloLibs}`.toLowerCase(); + console.info(`[Test Page]: ${testPage}`); + + await test.step('step-1: Go to the Marketo block full template test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + await expect(page.url()).toBe(testPage); + await expect(marketoBlock.email).toBeVisible({ timeout: 10000 }); + }); + + await test.step('step-2: check the input field placeholders', async () => { + await marketoBlock.checkInputPlaceholders(); + }); + + await test.step('step-3: Submit the form with valid inputs', async () => { + await marketoBlock.submitFullTemplateForm(); + }); + + await test.step('step-4: Verify the form submission redirect', async () => { + await expect(async () => { + await marketoBlock.submitButton.waitFor({ state: 'detached' }); + const redirectedUrl = await page.url(); + await expect(redirectedUrl).toContain('?submissionid'); + }).toPass(); + }); + }); + }); + + features[1].path.forEach((path) => { + test(`1: @marketo full template (redirect) with company type, ${features[1].tags}, path: ${path}`, async ({ + page, + baseURL, + }) => { + const testPage = `${baseURL}${path}${miloLibs}`.toLowerCase(); + console.info(`[Test Page]: ${testPage}`); + + await test.step('step-1: Go to the Marketo block full template test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + await expect(page.url()).toBe(testPage); + await expect(marketoBlock.email).toBeVisible({ timeout: 10000 }); + }); + + await test.step('step-2: check the input field placeholders', async () => { + await marketoBlock.checkInputPlaceholders(); + }); + + await test.step('step-3: Submit the form with valid inputs', async () => { + await marketoBlock.submitFullTemplateForm('Digital commerce'); + }); + + await test.step('step-4: Verify the form submission redirect', async () => { + await expect(async () => { + await marketoBlock.submitButton.waitFor({ state: 'detached' }); + const redirectedUrl = await page.url(); + await expect(redirectedUrl).toContain('?submissionid'); + }).toPass(); + }); + }); + }); + + features[2].path.forEach((path) => { + test(`2: @marketo expanded template (redirect), ${features[2].tags}}, path: ${path}`, async ({ + page, + baseURL, + }) => { + const testPage = `${baseURL}${path}${miloLibs}`.toLowerCase(); + console.info(`[Test Page]: ${testPage}`); + + await test.step('step-1: Go to the Marketo block expanded template test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + await expect(page.url()).toBe(testPage); + await expect(marketoBlock.email).toBeVisible({ timeout: 10000 }); + }); + + await test.step('step-2: check the input field placeholders', async () => { + await marketoBlock.checkInputPlaceholders(); + }); + + await test.step('step-3: Submit the form with valid inputs', async () => { + await marketoBlock.submitExpandedTemplateForm(); + }); + + await test.step('step-4: Verify the form submission redirect', async () => { + await expect(async () => { + await marketoBlock.submitButton.waitFor({ state: 'detached' }); + const redirectedUrl = await page.url(); + await expect(redirectedUrl).toContain('?submissionid'); + }).toPass(); + }); + }); + }); + + features[3].path.forEach((path) => { + test(`3: @marketo essential template (redirect), ${features[3].tags}, path: ${path}`, async ({ + page, + baseURL, + }) => { + const testPage = `${baseURL}${path}${miloLibs}`.toLowerCase(); + console.info(`[Test Page]: ${testPage}`); + + await test.step('step-1: Go to the Marketo block essential template test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + await expect(page.url()).toBe(testPage); + await expect(marketoBlock.email).toBeVisible({ timeout: 10000 }); + }); + + await test.step('step-2: check the input field placeholders', async () => { + await marketoBlock.checkInputPlaceholders(); + }); + + await test.step('step-3: Submit the form with valid inputs', async () => { + await marketoBlock.submitEssentialTemplateForm(); + }); + + await test.step('step-4: Verify the form submission redirect', async () => { + await expect(async () => { + await marketoBlock.submitButton.waitFor({ state: 'detached' }); + const redirectedUrl = await page.url(); + await expect(redirectedUrl).toContain('?submissionid'); + }).toPass(); + }); + }); + }); + + features[4].path.forEach((path) => { + test(`4: @marketo full template (message), ${features[4].tags}, path: ${path}`, async ({ + page, + baseURL, + }) => { + const testPage = `${baseURL}${path}${miloLibs}`.toLowerCase(); + console.info(`[Test Page]: ${testPage}`); + + await test.step('step-1: Go to the Marketo block full template test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + await expect(page.url()).toBe(testPage); + await expect(marketoBlock.email).toBeVisible({ timeout: 10000 }); + }); + + await test.step('step-2: check the input field placeholders', async () => { + await marketoBlock.checkInputPlaceholders(); + }); + + await test.step('step-3: Submit the form with valid inputs', async () => { + await marketoBlock.submitFullTemplateForm(); + }); + + await test.step('step-4: Verify the form submission redirect', async () => { + await expect(async () => { + await expect(marketoBlock.message).toBeAttached(); + await expect(page.url()).toBe(testPage); + }).toPass(); + }); + }); + }); + + features[5].path.forEach((path) => { + test(`5: @marketo full template (message) with company type, ${features[5].tags}, path: ${path}`, async ({ + page, + baseURL, + }) => { + const testPage = `${baseURL}${path}${miloLibs}`.toLowerCase(); + console.info(`[Test Page]: ${testPage}`); + + await test.step('step-1: Go to the Marketo block full template test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + await expect(page.url()).toBe(testPage); + await expect(marketoBlock.email).toBeVisible({ timeout: 10000 }); + }); + + await test.step('step-2: check the input field placeholders', async () => { + await marketoBlock.checkInputPlaceholders(); + }); + + await test.step('step-3: Submit the form with valid inputs', async () => { + await marketoBlock.submitFullTemplateForm('Digital commerce'); + }); + + await test.step('step-4: Verify the form submission redirect', async () => { + await expect(async () => { + await expect(marketoBlock.message).toBeAttached(); + await expect(page.url()).toBe(testPage); + }).toPass(); + }); + }); + }); + + features[6].path.forEach((path) => { + test(`6: @marketo expanded (message) template, ${features[6].tags}}, path: ${path}`, async ({ + page, + baseURL, + }) => { + const testPage = `${baseURL}${path}${miloLibs}`.toLowerCase(); + console.info(`[Test Page]: ${testPage}`); + + await test.step('step-1: Go to the Marketo block expanded template test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + await expect(page.url()).toBe(testPage); + await expect(marketoBlock.email).toBeVisible({ timeout: 10000 }); + }); + + await test.step('step-2: check the input field placeholders', async () => { + await marketoBlock.checkInputPlaceholders(); + }); + + await test.step('step-3: Submit the form with valid inputs', async () => { + await marketoBlock.submitExpandedTemplateForm(); + }); + + await test.step('step-4: Verify the form submission redirect', async () => { + await expect(async () => { + await expect(marketoBlock.message).toBeAttached(); + await expect(page.url()).toBe(testPage); + }).toPass(); + }); + }); + }); + + features[7].path.forEach((path) => { + test(`7: @marketo essential (message) template, ${features[7].tags}, path: ${path}`, async ({ + page, + baseURL, + }) => { + const testPage = `${baseURL}${path}${miloLibs}`.toLowerCase(); + console.info(`[Test Page]: ${testPage}`); + + await test.step('step-1: Go to the Marketo block essential template test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + await expect(page.url()).toBe(testPage); + await expect(marketoBlock.email).toBeVisible({ timeout: 10000 }); + }); + + await test.step('step-2: check the input field placeholders', async () => { + await marketoBlock.checkInputPlaceholders(); + }); + + await test.step('step-3: Submit the form with valid inputs', async () => { + await marketoBlock.submitEssentialTemplateForm(); + }); + + await test.step('step-4: Verify the form submission redirect', async () => { + await expect(async () => { + await expect(marketoBlock.message).toBeAttached(); + await expect(page.url()).toBe(testPage); + }).toPass(); + }); + }); + }); +}); diff --git a/nala/blocks/marquee/marquee.page.js b/nala/blocks/marquee/marquee.page.js new file mode 100644 index 0000000000..84673f0110 --- /dev/null +++ b/nala/blocks/marquee/marquee.page.js @@ -0,0 +1,186 @@ +export default class Marquee { + constructor(page, nth = 0) { + this.page = page; + // marquee types locators + this.marquee = page.locator('.marquee').nth(nth); + this.marqueeLight = page.locator('.marquee.light'); + this.marqueeSmall = page.locator('.marquee.small'); + this.marqueeSmallLight = page.locator('.marquee.small.light'); + this.marqueeSmallDark = page.locator('.marquee.small.dark'); + this.marqueeLarge = page.locator('.marquee.large'); + this.marqueeLargeLight = page.locator('.marquee.large.light'); + this.marqueeLargeDark = page.locator('.marquee.large.dark'); + this.marqueeQuiet = page.locator('.marquee.quiet'); + this.marqueeInline = page.locator('.marquee'); + this.marqueeSplitSmall = page.locator('.marquee.split.small'); + this.marqueeSplitLarge = page.locator('.marquee.split.large'); + this.marqueeSplitLargeLight = page.locator('.marquee.split.one-third.large.light'); + this.marqueeSplitOneThirdLargeLight = page.locator('.marquee.split.one-third.large.light'); + this.marqueeSplitOneThird = page.locator('.marquee.split.one-third'); + this.marqueeSplitOneThirdSmallLight = page.locator('.marquee.split.one-third.small.light'); + + // marque section(s) locators + // marquee details + this.detailM = this.marquee.locator('.detail-m'); + this.detailL = this.marquee.locator('.detail-l'); + this.brandImage = this.marquee.locator('.detail-m'); + + // marquee headings + this.headingXL = this.marquee.locator('.heading-xl'); + this.headingXXL = this.marquee.locator('.heading-xxl'); + + // marquee body area + this.bodyM = this.marquee.locator('.body-m'); + this.bodyXL = this.marquee.locator('.body-xl'); + + // marquee actions area + this.actionArea = this.marquee.locator('.action-area'); + this.outlineButton = this.marquee.locator('.con-button.outline'); + this.outlineButtonS = this.marquee.locator('.con-button.outline.button-s'); + this.outlineButtonM = this.marquee.locator('.con-button.outline.button-m'); + this.outlineButtonL = this.marquee.locator('.con-button.outline.button-l'); + this.outlineButtonXL = this.marquee.locator('.con-button.outline.button-xl'); + + this.blueButton = this.marquee.locator('.con-button.blue'); + this.blueButtonL = this.marquee.locator('.con-button.blue.button-l'); + this.blueButtonXL = this.marquee.locator('.con-button.blue.button-xl'); + this.filledBlueButton = this.marquee.locator('.con-button.blue'); + this.filledButtonM = this.marquee.locator('.con-button.blue.button-s'); + this.filledButtonM = this.marquee.locator('.con-button.blue.button-m'); + this.filledButtonL = this.marquee.locator('.con-button.blue.button-l'); + this.filledButtonXL = this.marquee.locator('.con-button.blue.button-xl'); + + this.actionLink1 = this.marquee.locator('a').nth(0); + this.actionLink2 = this.marquee.locator('a').nth(1); + + // background images + this.background = this.marquee.locator('.background'); + this.backgroundImage = this.marquee.locator('div.background img'); + this.backgroundImageMobile = this.marquee.locator('div .background .mobile-only img'); + this.backgroundImageTablet = this.marquee.locator('div.background .tablet-only img'); + this.backgroundImageDesktop = this.marquee.locator('div.background .desktop-only img'); + + // background video + this.backgroundVideo = this.marquee.locator('div video'); + this.backgroundVideoDesktop = this.marquee.locator('div .desktop-only video'); + + // foreground images + this.foreground = this.marquee.locator('.foreground'); + this.foregroundImage = this.marquee.locator('div.foreground img'); + this.iconImage = this.foreground.locator('.icon-area img'); + + // media images + this.mediaImage = this.marquee.locator('div.asset img'); + + // marquee attributes + this.attributes = { + 'marquee.light': { + backgroundImg: { + loading: 'eager', + fetchpriority: 'high', + width: '1369', + height: '685', + }, + }, + 'marquee.small': { + backgroundImg: { + loading: 'eager', + fetchpriority: 'high', + width: '750', + height: '375', + }, + }, + 'marquee.small.light': { + backgroundImg: { + loading: 'eager', + fetchpriority: 'high', + width: '750', + height: '375', + }, + }, + 'marquee.large': { + backgroundImg: { + loading: 'eager', + fetchpriority: 'high', + width: '750', + height: '375', + }, + }, + 'marquee.large.light': { + backgroundImg: { + loading: 'eager', + fetchpriority: 'high', + width: '750', + height: '375', + style: 'object-position: left center;', + }, + }, + 'marquee.split.small': { style: /^background:\s+rgb\(0, 0, 0\)$/ }, + 'marquee.split.large': { + iconImg: { + loading: 'eager', + fetchpriority: 'high', + width: '49', + height: '48', + }, + mediaImg: { + loading: 'eager', + fetchpriority: 'high', + width: '720', + height: '520', + }, + }, + 'marquee.split.one-third-large': { + style: /^background:\s+rgb\(245, 245, 245\)$/, + iconImg: { + loading: 'eager', + fetchpriority: 'high', + width: '200', + height: '80', + }, + mediaImg: { + loading: 'eager', + fetchpriority: 'high', + width: '720', + height: '520', + }, + }, + 'marquee.split.one-third': { + style: /^background:\s+rgb\(0, 0, 0\)$/, + iconImg: { + loading: 'eager', + fetchpriority: 'high', + width: '65', + height: '64', + }, + mediaImg: { + loading: 'eager', + fetchpriority: 'high', + width: '720', + height: '520', + }, + }, + backgroundMobileImg: { + loading: 'eager', + fetchpriority: 'high', + }, + 'backgroundVideo.inline': { + playsinline: '', + autoplay: '', + loop: '', + muted: '', + }, + 'backgroundVideo.loopOnce': { + playsinline: '', + autoplay: '', + muted: '', + }, + 'backgroundVideo.controls': { + controls: '', + autoplay: '', + loop: '', + muted: '', + }, + }; + } +} diff --git a/nala/blocks/marquee/marquee.spec.js b/nala/blocks/marquee/marquee.spec.js new file mode 100644 index 0000000000..87b18b33ee --- /dev/null +++ b/nala/blocks/marquee/marquee.spec.js @@ -0,0 +1,187 @@ +module.exports = { + name: 'Marquee Block', + features: [ + { + tcid: '0', + name: '@Marquee (light)', + path: '/drafts/nala/blocks/marquee/marquee-light', + data: { + h2Text: 'Heading XL Marquee standard medium left light', + bodyText: 'Body M Lorem ipsum dolor sit amet,', + outlineButtonText: 'Lorem ipsum', + blueButtonText: 'Call to action', + }, + tags: '@marquee @marquee-light @smoke @regression @milo', + }, + { + tcid: '1', + name: '@Marquee (small)', + path: '/drafts/nala/blocks/marquee/marquee-small', + data: { + h2Text: 'Marquee standard small dark', + bodyText: 'Lorem ipsum dolor sit amet', + blueButtonText: 'Call to action', + }, + tags: '@marquee @marquee-small @smoke @regression @milo', + }, + { + tcid: '2', + name: '@Marquee (small,light)', + path: '/drafts/nala/blocks/marquee/marquee-small-light', + data: { + detailText: 'Detail', + h2Text: 'Heading XL Marquee standard small light', + bodyText: 'Lorem ipsum dolor sit amet', + outlineButtonText: 'Lorem ipsum', + blueButtonText: 'Call to action', + }, + tags: '@marquee @marquee-small-light @smoke @regression @milo', + }, + { + tcid: '3', + name: '@Marquee (large)', + path: '/drafts/nala/blocks/marquee/marquee-large', + data: { + h2Text: 'Marquee Large Dark', + bodyText: 'Lorem ipsum dolor sit amet', + outlineButtonText: 'Lorem ipsum', + blueButtonText: 'Call to action', + }, + tags: '@marquee @marquee-large @smoke @regression @milo', + }, + { + tcid: '4', + name: '@Marquee (large,light)', + path: '/drafts/nala/blocks/marquee/marquee-large-light', + data: { + h2Text: 'Marquee Large Light', + bodyText: 'Lorem ipsum dolor sit amet', + outlineButtonText: 'Secondary action', + blueButtonText: 'Call to action', + }, + tags: '@marquee @marquee-large-light @smoke @regression @milo', + }, + { + tcid: '5', + name: '@Marquee (quiet)', + path: '/drafts/nala/blocks/marquee/marquee-quiet', + data: { + detailText: 'Detail', + h2Text: 'Marquee quiet', + bodyText: 'Marquee’s variants are small,', + blueButtonText: 'Watch the video', + }, + tags: '@marquee @marquee-quiet @smoke @regression @milo', + }, + { + tcid: '6', + name: '@Marquee (inline)', + path: '/drafts/nala/blocks/marquee/marquee-inline', + data: { + detailText: 'Detail', + h2Text: 'Marquee inline', + bodyText: 'Marquee’s variants are small,', + }, + tags: '@marquee @marquee-inline @smoke @regression @milo', + }, + { + tcid: '7', + name: '@Marquee (split,small)', + path: '/drafts/nala/blocks/marquee/marquee-split-small', + data: { + detailText: 'DETAIL M BOLD 12/15 OPTIONAL', + h2Text: 'Marquee Split ½ dark', + bodyText: 'Lorem ipsum dolor sit amet', + outlineButtonText: 'Secondary action', + blueButtonText: 'Call to action', + }, + tags: '@marquee @marquee-split-small @smoke @regression @milo', + }, + { + tcid: '8', + name: '@Marquee (split,large)', + path: '/drafts/nala/blocks/marquee/marquee-split-large', + data: { + detailText: 'DETAIL L BOLD 16/20', + h2Text: 'Heading XXL 44/55 Lorem', + bodyText: 'Body XL Regular (22/33) Lorem ipsum dolor sit amet', + blueButtonText: 'Call to action', + linkText: 'Body M 18/27', + }, + tags: '@marquee @marquee-split-large @smoke @regression @milo', + }, + { + tcid: '9', + name: '@Marquee (split,one-third,large,light)', + path: '/drafts/nala/blocks/marquee/marquee-split-one-third-large-light', + data: { + detailText: 'DETAIL L BOLD 16/20', + h2Text: 'Heading XXL 44/55 Lorem', + bodyText: 'Body XL Regular (22/33) Lorem ipsum dolor sit amet', + blueButtonText: 'Call to action', + linkText: 'Body M 18/27', + }, + tags: '@marquee @marquee-split-one-third-large-light @smoke @regression @milo', + }, + { + tcid: '10', + name: '@Marquee (split,one-third)', + path: '/drafts/nala/blocks/marquee/marquee-split-one-third', + data: { + detailText: 'DETAIL M BOLD 12/15 OPTIONAL', + h2Text: 'Heading XL 36/45 Lorem', + bodyText: 'Body M Regular (18/27) Lorem ipsum dolor sit amet', + blueButtonText: 'Call to action', + linkText: 'Body M 18/27', + }, + tags: '@marquee @marquee-split-one-third @smoke @regression @milo', + }, + { + tcid: '11', + name: '@Marquee (split,one-third,small,light)', + path: '/drafts/nala/blocks/marquee/marquee-split-one-third-small-light', + data: { + detailText: 'DETAIL M BOLD 12/15 OPTIONAL', + h2Text: 'Heading XL 36/45 Lorem', + bodyText: 'Body M Regular (18/27) Lorem ipsum dolor sit amet', + blueButtonText: 'Call to action', + }, + tags: '@marquee @marquee-split-one-third-small-light @smoke @regression @milo', + }, + { + tcid: '12', + name: '@Marquee small (background video playsinline)', + path: '/drafts/nala/blocks/marquee/marquee-small-background-video', + data: { + h2Text: 'Marquee standard small dark', + bodyText: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit', + blueButtonText: 'Call to action', + }, + tags: '@marquee @marquee-video @smoke @regression @milo', + }, + { + tcid: '13', + name: '@Marquee large (background video playsinline desktop)', + path: '/drafts/nala/blocks/marquee/marquee-large-desktop-video-autoplay', + data: { + h2Text: 'Desktop video only', + bodyText: 'From amazing AI-generated images in Photoshop', + blueButtonText: 'Free trial', + linkText: 'See all plans', + }, + tags: '@marquee @marquee-video @smoke @regression @milo', + }, + { + tcid: '14', + name: '@Marquee large (background video playsinline loop once)', + path: '/drafts/nala/blocks/marquee/video-autoplay-loop-once', + data: { + detailText: 'DETAIL L 16/20', + h2Text: 'Heading XL 36/45 Media (large, dark)', + bodyText: 'Body M 18/27 Lorem ipsum dolor sit amet', + blueButtonText: 'Learn More', + }, + tags: '@marquee @marquee-video @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/marquee/marquee.test.js b/nala/blocks/marquee/marquee.test.js new file mode 100644 index 0000000000..a4f76da3f0 --- /dev/null +++ b/nala/blocks/marquee/marquee.test.js @@ -0,0 +1,573 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console, chai-friendly/no-unused-expressions */ +import { expect, test } from '@playwright/test'; +import WebUtil from '../../libs/webutil.js'; +import { features } from './marquee.spec.js'; +import MarqueeBlock from './marquee.page.js'; + +let webUtil; +let marquee; +let consoleErrors = []; + +const miloLibs = process.env.MILO_LIBS || ''; +const knownConsoleErrors = [ + 'Access-Control-Allow-Origin', + 'Failed to load resource: net::ERR_FAILED', + 'adobeid-na1-stg1.services', + 'Attestation check for Topics', + 'Access to fetch at', + 'net::ERR_HTTP2_PROTOCOL_ERROR', +]; + +test.describe('Milo Marquee Block test suite', () => { + test.beforeEach(async ({ page }) => { + webUtil = new WebUtil(page); + marquee = new MarqueeBlock(page); + + page.on('console', (exception) => { + if (exception.type() === 'error') { + consoleErrors.push(exception.text()); + } + }); + }); + + test.afterEach(async () => { + consoleErrors = []; + }); + + // Test 0 : Marquee (light) + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + const { data } = features[0]; + + await test.step('step-1: Go to Marquee (light) block test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify marquee(light) specs', async () => { + await expect(await marquee.marqueeLight).toBeVisible(); + + await expect(await marquee.headingXL).toContainText(data.h2Text); + await expect(await marquee.bodyM).toContainText(data.bodyText); + await expect(await marquee.outlineButton).toContainText(data.outlineButtonText); + await expect(await marquee.blueButton).toContainText(data.blueButtonText); + + await expect(await marquee.backgroundImage).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.backgroundImage, marquee.attributes['marquee.light'].backgroundImg)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await marquee.marqueeLight).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + await expect(await marquee.outlineButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.outlineButtonText, 1, data.h2Text)); + await expect(await marquee.blueButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 2, data.h2Text)); + }); + + await test.step('step-4: Verify browser console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 1 : Marquee (small) + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + const { data } = features[1]; + + await test.step('step-1: Go to Marquee (small) block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (small) specs', async () => { + await expect(await marquee.marqueeSmall).toBeVisible(); + + await expect(await marquee.headingXL).toContainText(data.h2Text); + await expect(await marquee.bodyM).toContainText(data.bodyText); + await expect(await marquee.blueButton).toContainText(data.blueButtonText); + + await expect(await marquee.backgroundImage).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.backgroundImage, marquee.attributes['marquee.small'].backgroundImg)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeSmall).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + await expect(await marquee.blueButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + }); + + await test.step('step-4: Verify browser console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 2 : Marquee (small,light) + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + const { data } = features[2]; + + await test.step('step-1: Go to Marquee (small, light ) block test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (small, light) specs', async () => { + await expect(await marquee.marqueeSmallLight).toBeVisible(); + + await expect(await marquee.detailM).toContainText(data.detailText); + await expect(await marquee.headingXL).toContainText(data.h2Text); + await expect(await marquee.bodyM).toContainText(data.bodyText); + await expect(await marquee.outlineButton).toContainText(data.outlineButtonText); + await expect(await marquee.blueButton).toContainText(data.blueButtonText); + + await expect(await marquee.backgroundImage).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.backgroundImage, marquee.attributes['marquee.small.light'].backgroundImg)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeSmallLight).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + await expect(await marquee.outlineButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.outlineButtonText, 1, data.h2Text)); + await expect(await marquee.blueButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 2, data.h2Text)); + }); + + await test.step('step-4: Verify browser console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 3 : Marquee (large) + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[3].path}${miloLibs}`); + const { data } = features[3]; + + await test.step('step-1: Go to Marquee (large ) block test page', async () => { + await page.goto(`${baseURL}${features[3].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[3].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (large) specs', async () => { + await expect(await marquee.marqueeLarge).toBeVisible(); + + await expect(await marquee.headingXXL).toContainText(data.h2Text); + await expect(await marquee.bodyXL).toContainText(data.bodyText); + await expect(await marquee.outlineButtonXL).toContainText(data.outlineButtonText); + await expect(await marquee.blueButtonXL).toContainText(data.blueButtonText); + + await expect(await marquee.backgroundImage).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.backgroundImage, marquee.attributes['marquee.large'].backgroundImg)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeLarge).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + await expect(await marquee.outlineButtonXL).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.outlineButtonText, 1, data.h2Text)); + await expect(await marquee.blueButtonXL).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 2, data.h2Text)); + }); + + await test.step('step-4: Verify browser console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 4 : Marquee (large,light) + test(`${features[4].name},${features[4].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[4].path}${miloLibs}`); + const { data } = features[4]; + + await test.step('step-1: Go to Marquee (large, light ) block test page', async () => { + await page.goto(`${baseURL}${features[4].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[4].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (large, light) specs', async () => { + await expect(await marquee.marqueeLargeLight).toBeVisible(); + + await expect(await marquee.headingXXL).toContainText(data.h2Text); + await expect(await marquee.bodyXL).toContainText(data.bodyText); + await expect(await marquee.outlineButtonXL).toContainText(data.outlineButtonText); + await expect(await marquee.blueButtonXL).toContainText(data.blueButtonText); + + await expect(await marquee.backgroundImage).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.backgroundImage, marquee.attributes['marquee.large.light'].backgroundImg)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeLargeLight).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + await expect(await marquee.outlineButtonXL).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.outlineButtonText, 1, data.h2Text)); + await expect(await marquee.blueButtonXL).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 2, data.h2Text)); + }); + + await test.step('step-4: Verify and log if any console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 5 : Marquee (quiet) + test(`${features[5].name},${features[5].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[5].path}${miloLibs}`); + const { data } = features[5]; + + await test.step('step-1: Go to Marquee (quiet ) block test page', async () => { + await page.goto(`${baseURL}${features[5].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[5].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (quiet) specs', async () => { + await expect(await marquee.marqueeQuiet).toBeVisible(); + + await expect(await marquee.detailM).toContainText(data.detailText); + await expect(await marquee.headingXL).toContainText(data.h2Text); + await expect(await marquee.bodyM).toContainText(data.bodyText); + await expect(await marquee.blueButton).toContainText(data.blueButtonText); + + await expect(await marquee.backgroundImage).toBeHidden(); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeQuiet).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + await expect(await marquee.blueButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + }); + + await test.step('step-4: Verify and log if any console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 6 : Marquee (inline) + test(`${features[6].name},${features[6].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[6].path}${miloLibs}`); + const { data } = features[6]; + + await test.step('step-1: Go to Marquee (inline ) block test page', async () => { + await page.goto(`${baseURL}${features[6].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[6].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (inline) specs', async () => { + await expect(await marquee.marqueeInline).toBeVisible(); + + await expect(await marquee.detailM).toContainText(data.detailText); + await expect(await marquee.headingXL).toContainText(data.h2Text); + await expect(await marquee.bodyM).toContainText(data.bodyText); + + await expect(await marquee.backgroundImage).toBeHidden(); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeInline).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + }); + + await test.step('step-4: Verify and log if any console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 7 : Marquee (split,small) + test(`${features[7].name},${features[7].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[7].path}${miloLibs}`); + const { data } = features[7]; + + await test.step('step-1: Go to Marquee (split, small ) block test page', async () => { + await page.goto(`${baseURL}${features[7].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[7].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (split, small) specs', async () => { + await expect(marquee.marqueeSplitSmall).toBeVisible(); + + await expect(await marquee.detailM).toContainText(data.detailText); + await expect(await marquee.headingXL).toContainText(data.h2Text); + await expect(await marquee.bodyM).toContainText(data.bodyText); + await expect(await marquee.outlineButton).toContainText(data.outlineButtonText); + await expect(await marquee.blueButton).toContainText(data.blueButtonText); + + expect(await webUtil.verifyAttributes(marquee.marqueeSplitSmall, marquee.attributes['marquee.split.small'].style)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeSplitSmall).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + await expect(await marquee.outlineButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.outlineButtonText, 1, data.h2Text)); + await expect(await marquee.blueButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 2, data.h2Text)); + }); + + await test.step('step-4: Verify and log if any console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 8 : Marquee (split,large) + test(`${features[8].name},${features[8].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[8].path}${miloLibs}`); + const { data } = features[8]; + + await test.step('step-1: Go to Marquee (split, large ) block test page', async () => { + await page.goto(`${baseURL}${features[8].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[8].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (split, large) specs ', async () => { + await expect(await marquee.marqueeSplitLarge).toBeVisible(); + + await expect(await marquee.detailL).toContainText(data.detailText); + await expect(await marquee.headingXXL).toContainText(data.h2Text); + await expect(await marquee.bodyXL).toContainText(data.bodyText); + await expect(await marquee.blueButtonXL).toContainText(data.blueButtonText); + await expect(await marquee.actionLink2).toContainText(data.linkText); + + await expect(await marquee.iconImage).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.iconImage, marquee.attributes['marquee.split.large'].iconImg)).toBeTruthy(); + + await expect(await marquee.mediaImage).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.mediaImage, marquee.attributes['marquee.split.large'].mediaImg)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeSplitLarge).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + await expect(await marquee.blueButtonXL).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + await expect(await marquee.actionLink2).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.linkText, 2, data.h2Text)); + }); + + await test.step('step-4: Verify and log if any console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 9 : Marquee (split,one-third,large,light) + test(`${features[9].name},${features[9].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[9].path}${miloLibs}`); + const { data } = features[9]; + + await test.step('step-1: Go to Marquee (split, one-third, large, light ) block test page', async () => { + await page.goto(`${baseURL}${features[9].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[9].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (split, one-third, large, light) specs', async () => { + await expect(marquee.marqueeSplitOneThirdLargeLight).toBeVisible(); + + await expect(await marquee.detailL).toContainText(data.detailText); + await expect(await marquee.headingXXL).toContainText(data.h2Text); + await expect(await marquee.bodyXL).toContainText(data.bodyText); + await expect(await marquee.blueButtonXL).toContainText(data.blueButtonText); + await expect(await marquee.actionLink2).toContainText(data.linkText); + + await expect(await marquee.iconImage).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.iconImage, marquee.attributes['marquee.split.one-third-large'].iconImg)).toBeTruthy(); + + await expect(await marquee.mediaImage).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.mediaImage, marquee.attributes['marquee.split.one-third-large'].mediaImg)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeSplitOneThirdLargeLight).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + await expect(await marquee.blueButtonXL).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + await expect(await marquee.actionLink2).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.linkText, 2, data.h2Text)); + }); + + await test.step('step-3: Verify and log if any console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 10 : Marquee (split,one-third) + test(`${features[10].name},${features[10].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[10].path}${miloLibs}`); + const { data } = features[10]; + + await test.step('step-1: Go to Marquee (split, one-third ) block test page', async () => { + await page.goto(`${baseURL}${features[10].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[10].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (split, one-third) specs', async () => { + await expect(await marquee.marqueeSplitOneThird).toBeVisible(); + + await expect(await marquee.detailM).toContainText(data.detailText); + await expect(await marquee.headingXL).toContainText(data.h2Text); + await expect(await marquee.bodyM).toContainText(data.bodyText); + await expect(await marquee.blueButtonL).toContainText(data.blueButtonText); + await expect(await marquee.actionLink2).toContainText(data.linkText); + + await expect(await marquee.iconImage).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.iconImage, marquee.attributes['marquee.split.one-third'].iconImg)).toBeTruthy(); + + await expect(await marquee.mediaImage).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.mediaImage, marquee.attributes['marquee.split.one-third'].mediaImg)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeSplitOneThird).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + await expect(await marquee.blueButtonL).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + await expect(await marquee.actionLink2).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.linkText, 2, data.h2Text)); + }); + + await test.step('step-4: Verify and log if any console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 11 : Marquee (split,one-third,small,light) + test(`${features[11].name},${features[11].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[11].path}${miloLibs}`); + const { data } = features[11]; + + await test.step('step-1: Go to Marquee (split,one-third,small,light ) block test page', async () => { + await page.goto(`${baseURL}${features[11].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[11].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (split,one-third,small,light) specs', async () => { + await expect(marquee.marqueeSplitOneThirdSmallLight).toBeVisible(); + + await expect(await marquee.detailM).toContainText(data.detailText); + await expect(await marquee.headingXL).toContainText(data.h2Text); + await expect(await marquee.bodyM).toContainText(data.bodyText); + await expect(await marquee.blueButtonL).toContainText(data.blueButtonText); + + await expect(await marquee.mediaImage).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.mediaImage, marquee.attributes['marquee.split.one-third'].mediaImg)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeSplitOneThirdSmallLight).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + await expect(await marquee.blueButtonL).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + }); + + await test.step('step-4: Verify and log if any console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 12 : Marquee small (background video playsinline) + test(`${features[12].name},${features[12].tags}`, async ({ page, baseURL, browserName }) => { + test.slow(); + test.skip(browserName === 'webkit', 'This feature is failing on Webkit browsers'); + console.info(`[Test Page]: ${baseURL}${features[12].path}${miloLibs}`); + const { data } = features[12]; + + await test.step('step-1: Go to Marquee (small) block test page', async () => { + await page.goto(`${baseURL}${features[12].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[12].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (small) background video playsinline specs', async () => { + await expect(await marquee.marqueeSmallDark).toBeVisible(); + + await expect(await marquee.headingXL).toContainText(data.h2Text); + await expect(await marquee.bodyM).toContainText(data.bodyText); + await expect(await marquee.blueButton).toContainText(data.blueButtonText); + + await expect(await marquee.backgroundVideo).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.backgroundVideo, marquee.attributes['backgroundVideo.inline'])).toBeTruthy(); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeSmallDark).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + await expect(await marquee.blueButtonL).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + }); + + await test.step('step-4: Verify and log if any console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 13 : Marquee large (background video playsinline desktop) + test(`${features[13].name},${features[13].tags}`, async ({ page, baseURL, browserName }) => { + test.skip(browserName === 'webkit', 'This feature is failing on Webkit browsers'); + test.slow(); + console.info(`[Test Page]: ${baseURL}${features[13].path}${miloLibs}`); + const { data } = features[13]; + + await test.step('step-1: Go to Marquee (large, light ) block test page', async () => { + await page.goto(`${baseURL}${features[13].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[13].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (large, light) desktop background specs', async () => { + await expect(await marquee.marqueeLargeLight).toBeVisible(); + + await expect(await marquee.headingXXL).toContainText(data.h2Text); + await expect(await marquee.bodyXL).toContainText(data.bodyText); + await expect(await marquee.blueButtonXL).toContainText(data.blueButtonText); + await expect(await marquee.actionLink2).toContainText(data.linkText); + + await expect(await marquee.backgroundVideoDesktop).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.backgroundVideoDesktop, marquee.attributes['backgroundVideo.inline'])).toBeTruthy(); + + const sourceElement = await marquee.backgroundVideoDesktop.locator('source'); + expect(await sourceElement.getAttribute('src')).toContain('.mp4'); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeLargeLight).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 1)); + await expect(await marquee.blueButtonXL).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + await expect(await marquee.actionLink2).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.linkText, 2, data.h2Text)); + }); + + await test.step('step-4: Verify and log if any console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); + + // Test 14 : Marquee large (background video playsinline loop once) + test(`${features[14].name},${features[14].tags}`, async ({ page, baseURL }) => { + test.slow(); + console.info(`[Test Page]: ${baseURL}${features[14].path}${miloLibs}`); + const { data } = features[14]; + + await test.step('step-1: Go to Marquee (large, dark ) block test page', async () => { + await page.goto(`${baseURL}${features[14].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[14].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Marquee (large, dark) background specs', async () => { + await expect(await marquee.marqueeLargeDark).toBeVisible(); + + await expect(await marquee.headingXXL).toContainText(data.h2Text); + await expect(await marquee.bodyXL).toContainText(data.bodyText); + await expect(await marquee.blueButtonXL).toContainText(data.blueButtonText); + + await expect(await marquee.backgroundVideo).toBeVisible(); + expect(await webUtil.verifyAttributes(marquee.backgroundVideo, marquee.attributes['backgroundVideo.loopOnce'])).toBeTruthy(); + + const sourceElement = await marquee.backgroundVideo.locator('source'); + expect(await sourceElement.getAttribute('src')).toContain('.mp4'); + expect(await sourceElement.getAttribute('type')).toContain('video/mp4'); + }); + + await test.step('step-3: Verify analytic attributes', async () => { + await expect(await marquee.marqueeLargeDark).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('marquee', 2)); + await expect(await marquee.blueButtonXL).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + }); + + await test.step('step-4: Verify and log if any console errors', async () => { + consoleErrors.length > knownConsoleErrors.length && console.log('[Console error]:', consoleErrors); + expect.soft(consoleErrors.length).toBeLessThanOrEqual(knownConsoleErrors.length); + }); + }); +}); diff --git a/nala/blocks/media/media.page.js b/nala/blocks/media/media.page.js new file mode 100644 index 0000000000..c1cfe4e606 --- /dev/null +++ b/nala/blocks/media/media.page.js @@ -0,0 +1,79 @@ +export default class Media { + constructor(page, nth = 0) { + this.page = page; + // media types + this.media = page.locator('.media').nth(nth); + this.mediaSmall = page.locator('.media.small'); + this.mediaLargeDark = page.locator('.media.large'); + + // media details + this.detailM = this.media.locator('.detail-m'); + this.detailL = this.media.locator('.detail-l'); + + // media headings + this.headingXS = this.media.locator('.heading-xs'); + this.headingM = this.media.locator('.heading-m'); + this.headingXL = this.media.locator('.heading-xl'); + + // media body area + this.bodyS = this.media.locator('.body-s').nth(0); + this.bodyM = this.media.locator('.body-m').nth(0); + this.bodyXL = this.media.locator('.body-xl').nth(0); + this.bodyTextM = this.media.locator('p:nth-of-type(2)'); + this.bodyTextS = this.media.locator('p:nth-of-type(2)'); + + // media actions area + this.actionArea = this.media.locator('.action-area'); + this.outlineButton = this.media.locator('.con-button.outline'); + this.blueButton = this.media.locator('.con-button.blue'); + + // media image + this.mediaImage = this.media.locator('.image'); + this.mediaImg = this.mediaImage.locator('img'); + + // background video + this.backgroundVideo = this.media.locator('div video'); + this.backgroundVideoDesktop = this.media.locator('div .desktop-only video'); + + // media attributes + this.attributes = { + 'media.small': { + image: { + loading: 'eager', + fetchpriority: 'high', + width: '400', + height: '300', + }, + }, + 'media.large': { + image: { + loading: 'lazy', + width: '700', + height: '525', + }, + }, + 'backgroundVideo.inline': { + playsinline: '', + autoplay: '', + loop: '', + muted: '', + }, + 'backgroundVideo.loopOnce': { + playsinline: '', + autoplay: '', + muted: '', + }, + 'backgroundVideo.controls': { + controls: '', + autoplay: '', + loop: '', + muted: '', + }, + analytics: { + 'media.daa-lh': { 'daa-lh': /b[1-9]|media|default|default/ }, + 'section.daa-lh': { 'daa-lh': /s[1-9]/ }, + 'content.daa-lh': { 'daa-lh': /b[1-9]|content|default|default/ }, + }, + }; + } +} diff --git a/nala/blocks/media/media.spec.js b/nala/blocks/media/media.spec.js new file mode 100644 index 0000000000..015b928cac --- /dev/null +++ b/nala/blocks/media/media.spec.js @@ -0,0 +1,66 @@ +module.exports = { + BlockName: 'Media Block', + features: [ + { + tcid: '0', + name: '@Media (small)', + path: '/drafts/nala/blocks/media/media-small', + data: { + detailText: 'Detail M 12/15', + h2Text: 'Heading XS 18/22 Media (small)', + bodyText: 'Body S 16/24 Lorem ipsum dolor sit amet', + blueButtonText: 'Learn More', + outlineButtonText: 'Watch the Video', + }, + tags: '@media @media-small @smoke @regression @milo', + }, + { + tcid: '1', + name: '@Media', + path: '/drafts/nala/blocks/media/media', + data: { + detailText: 'Detail M 12/15', + h2Text: 'Heading M 24/30 Media', + bodyText: 'Body S 16/24 Lorem ipsum dolor sit amet', + blueButtonText: 'Learn More', + }, + tags: '@media @smoke @regression @milo', + }, + { + tcid: '2', + name: '@media (large, dark)', + path: '/drafts/nala/blocks/media/media-large-dark', + data: { + detailText: 'Detail L 16/20', + h2Text: 'Heading XL 36/45 Media (large, dark)', + bodyText: 'Body M 18/27 Lorem ipsum dolor sit amet,', + blueButtonText: 'Learn More', + }, + tags: '@media @media-large-dark @smoke @regression @milo', + }, + { + tcid: '3', + name: '@media (large, dark) video, autoplay infinite looping', + path: '/drafts/nala/blocks/media/media-video-autoplay-infinite-loop', + data: { + detailText: 'Detail L 16/20', + h2Text: 'Heading XL 36/45 Media (large, dark)', + bodyText: 'Body M 18/27 Lorem ipsum dolor sit amet,', + blueButtonText: 'Learn More', + }, + tags: '@media @media-video @smoke @regression @milo', + }, + { + tcid: '4', + name: '@media video, autoplay loop once', + path: '/drafts/nala/blocks/media/media-video-autoplay-loop-once', + data: { + detailText: 'Detail L 16/20', + h2Text: 'Heading XL 36/45 Media (large, dark)', + bodyText: 'Body M 18/27 Lorem ipsum dolor sit amet,', + blueButtonText: 'Learn More', + }, + tags: '@media @media-video @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/media/media.test.js b/nala/blocks/media/media.test.js new file mode 100644 index 0000000000..c245227907 --- /dev/null +++ b/nala/blocks/media/media.test.js @@ -0,0 +1,166 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import WebUtil from '../../libs/webutil.js'; +import { features } from './media.spec.js'; +import MediaBlock from './media.page.js'; + +let webUtil; +let media; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Media Block test suite', () => { + test.beforeEach(async ({ page }) => { + webUtil = new WebUtil(page); + media = new MediaBlock(page); + }); + + // Test 0 : Media (small) + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + const { data } = features[0]; + + await test.step('step-1: Go to Media (small) block test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify media (small) block specs', async () => { + await expect(media.mediaSmall).toBeVisible(); + + await expect(await media.detailM).toContainText(data.detailText); + await expect(await media.headingXS).toContainText(data.h2Text); + await expect(await media.bodyS).toContainText(data.bodyText); + await expect(await media.outlineButton).toContainText(data.outlineButtonText); + await expect(await media.blueButton).toContainText(data.blueButtonText); + + await expect(await media.mediaImage).toBeVisible(); + expect(await webUtil.verifyAttributes(media.mediaImg, media.attributes['media.small'].image)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await media.mediaSmall).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('media', 1)); + await expect(await media.blueButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + }); + }); + + // Test 1 : Media + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + const { data } = features[1]; + + await test.step('step-1: Go to media block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify media block specs', async () => { + await expect(media.media).toBeVisible(); + + await expect(await media.detailM).toContainText(data.detailText); + await expect(await media.headingM).toContainText(data.h2Text); + await expect(await media.bodyS).toContainText(data.bodyText); + await expect(await media.blueButton).toContainText(data.blueButtonText); + + await expect(await media.mediaImage).toBeVisible(); + expect(await webUtil.verifyAttributes(media.mediaImg, media.attributes['media.small'].image)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await media.media).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('media', 1)); + await expect(await media.blueButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + }); + }); + + // Test 2 : Media (large, dark) + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + const { data } = features[2]; + + await test.step('step-1: Go to media block test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify media (large, dark) block specs', async () => { + await expect(media.mediaLargeDark).toBeVisible(); + + await expect(await media.detailL).toContainText(data.detailText); + await expect(await media.headingXL).toContainText(data.h2Text); + await expect(await media.bodyM).toContainText(data.bodyText); + await expect(await media.blueButton).toContainText(data.blueButtonText); + + await expect(await media.mediaImage).toBeVisible(); + expect(await webUtil.verifyAttributes(media.mediaImg, media.attributes['media.large'].image)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await media.mediaLargeDark).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('media', 1)); + await expect(await media.blueButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + }); + }); + + // Test 3 : Media (large, dark) video, autoplay infinite looping + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL, browserName }) => { + test.skip(browserName === 'webkit', 'This feature is failing on Webkit browsers'); + test.slow(); + console.info(`[Test Page]: ${baseURL}${features[3].path}${miloLibs}`); + const { data } = features[3]; + + await test.step('step-1: Go to media block test page', async () => { + await page.goto(`${baseURL}${features[3].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[3].path}${miloLibs}`); + }); + + await test.step('step-2: Verify media (large, dark) block specs', async () => { + await expect(media.mediaLargeDark).toBeVisible(); + + await expect(await media.detailL).toContainText(data.detailText); + await expect(await media.headingXL).toContainText(data.h2Text); + await expect(await media.bodyM).toContainText(data.bodyText); + await expect(await media.blueButton).toContainText(data.blueButtonText); + + await expect(await media.backgroundVideo).toBeVisible(); + expect(await webUtil.verifyAttributes(media.backgroundVideo, media.attributes['backgroundVideo.inline'])).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await media.mediaLargeDark).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('media', 2)); + await expect(await media.blueButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + }); + }); + + // Test 5 : Media (large, dark) video, autoplay loop once + test(`${features[4].name},${features[4].tags}`, async ({ page, baseURL }) => { + test.slow(); + console.info(`[Test Page]: ${baseURL}${features[4].path}${miloLibs}`); + const { data } = features[4]; + + await test.step('step-1: Go to media block test page', async () => { + await page.goto(`${baseURL}${features[4].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[4].path}${miloLibs}`); + }); + + await test.step('step-2: Verify media (large, dark) block specs', async () => { + await expect(media.mediaLargeDark).toBeVisible(); + + await expect(await media.detailL).toContainText(data.detailText); + await expect(await media.headingXL).toContainText(data.h2Text); + await expect(await media.bodyM).toContainText(data.bodyText); + await expect(await media.blueButton).toContainText(data.blueButtonText); + + await expect(await media.backgroundVideo).toBeVisible(); + expect(await webUtil.verifyAttributes(media.backgroundVideo, media.attributes['backgroundVideo.loopOnce'])).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await media.mediaLargeDark).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('media', 2)); + await expect(await media.blueButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.blueButtonText, 1, data.h2Text)); + }); + }); +}); diff --git a/nala/blocks/merchcard/merchcard.pages.js b/nala/blocks/merchcard/merchcard.pages.js new file mode 100644 index 0000000000..4bbac09c5c --- /dev/null +++ b/nala/blocks/merchcard/merchcard.pages.js @@ -0,0 +1,99 @@ +export default class Merchcard { + constructor(page, nth = 0) { + this.page = page; + + // merch card locators + this.merchCard = this.page.locator('.merch-card').nth(nth); + this.segment = this.page.locator('.merch-card.segment').nth(nth); + this.sepcialOffers = this.page.locator('.merch-card.special-offers').nth(nth); + this.plans = this.page.locator('.merch-card.plans').nth(nth); + this.catalog = this.page.locator('.merch-card.catalog').nth(nth); + + // inline price and strikethrough price + this.inlinePrice1 = this.merchCard.locator('span.placeholder-resolved').nth(0); + this.inlinePrice2 = this.merchCard.locator('span.placeholder-resolved').nth(1); + this.price = this.inlinePrice1.locator('.price'); + this.priceCurrencySymbol = this.inlinePrice1.locator('.price-currency-symbol'); + this.priceInteger = this.inlinePrice1.locator('.price-integer'); + this.priceDecimalDelimiter = this.inlinePrice1.locator('.price-decimals-delimiter'); + this.priceDecimals = this.inlinePrice1.locator('.price-decimals'); + this.priceRecurrence = this.inlinePrice1.locator('.price-recurrence'); + + this.strikethroughPrice = this.inlinePrice2.locator('.price'); + this.strikethroughPriceCurrencySymbol = this.inlinePrice2.locator('.price-currency-symbol'); + this.strikethroughPriceInteger = this.inlinePrice2.locator('.price-integer'); + this.strikethroughPriceDecimalDelimiter = this.inlinePrice2.locator('.price-decimals-delimiter'); + this.strikethroughPriceDecimals = this.inlinePrice2.locator('.price-decimals'); + this.strikethroughPriceRecurrence = this.inlinePrice2.locator('.price-recurrence'); + + // merch-card segment locators + this.segmentRibbon = this.merchCard.locator('.segment-badge'); + this.segmentTitle = this.segment.locator('h3[slot="heading-xs"]').nth(0); + this.segmentDescription1 = this.segment.locator('div[slot="body-xs"] p').nth(0); + this.segmentDescription2 = this.segment.locator('div[slot="body-xs"] p').nth(1); + + this.linkText1 = this.segmentDescription2.locator('a').nth(0); + this.linkText2 = this.segmentDescription2.locator('a').nth(1); + + // merch-card special offers + this.sepcialOffersImage = this.sepcialOffers.locator('div[slot="bg-image"] img'); + this.sepcialOffersRibbon = this.merchCard.locator('.special-offers-badge'); + this.sepcialOffersTitleH4 = this.sepcialOffers.locator('h4[slot="detail-m"]').nth(0); + this.sepcialOffersTitleH5 = this.sepcialOffers.locator('h5[slot="body-xs"]'); + this.sepcialOffersTitleH3 = this.sepcialOffers.locator('h3[slot="heading-xs"]').nth(0); + + this.sepcialOffersDescription1 = this.sepcialOffers.locator('div[slot="body-xs"] p').nth(1); + this.sepcialOffersDescription2 = this.sepcialOffers.locator('div[slot="body-xs"] p').nth(2); + this.sepcialOffersDescription3 = this.sepcialOffers.locator('div[slot="body-xs"] p').nth(3); + this.sepcialOffersLinkText3 = this.sepcialOffersDescription3.locator('a').nth(0); + + this.seeTermsTextLink = this.merchCard.locator('a:has-text("See terms")'); + + // merch-card plans locators + this.productIcon = this.plans.locator('img'); + this.plansRibbon = this.plans.locator('.plans-badge'); + this.plansCardTitleH3 = this.plans.locator('h3[slot="heading-xs"]'); + this.plansCardTitleH4 = this.plans.locator('h4[slot="body-xxs"]'); + this.plansCardTitleH5 = this.plans.locator('h5[slot="body-xxs"]'); + this.plansCardDescription1 = this.plans.locator('div[slot="body-xs"] p').nth(1); + this.plansCardDescription2 = this.plans.locator('div[slot="body-xs"] p').nth(2); + this.plansCardDescription3 = this.plans.locator('div[slot="body-xs"] p').nth(3); + this.seePlansTextLink = this.merchCard.locator('a:has-text("See plan & pricing details")'); + + // merch-card catalog + this.catalogProductIcon = this.catalog.locator('#shadow-root div.icons'); + this.catalogRibbon = this.catalog.locator('.catalog-badge'); + this.catalogActionMenu = this.catalog.locator('div[slot="action-menu-content"]'); + this.catalogActionMenuList = this.catalogActionMenu.locator('ul li'); + this.catalogActionMenuPText1 = this.catalogActionMenu.locator('p').nth(0); + this.catalogActionMenuPText2 = this.catalogActionMenu.locator('p').nth(1); + this.catalogActionMenuPText3 = this.catalogActionMenu.locator('p').nth(2); + this.catalogActionMenuPText4 = this.catalogActionMenu.locator('p').nth(3); + this.catalogActionMenuPText5 = this.catalogActionMenu.locator('p ').nth(4); + this.systemRequirementTextLink = this.merchCard.locator('a:has-text("See system requirements")'); + + this.catalogCardTitleH3 = this.catalog.locator('h3[slot="heading-xs"]'); + this.catalogCardTitleH4 = this.catalog.locator('h4[slot="body-xxs"]'); + this.catalogCardDescription2 = this.catalog.locator('div[slot="body-xs"] p').nth(2); + this.seeWhatsIncludedTextLink = this.merchCard.locator('a:has-text("See what’s included")'); + this.learnMoreTextLink = this.merchCard.locator('a:has-text("Learn more")'); + + // merch-card footer sections + this.footer = this.merchCard.locator('div[slot="footer"]'); + this.footerCheckbox = this.page.locator('#stock-checkbox input[type="checkbox"]'); + this.footerCheckboxLabel = this.merchCard.locator('#stock-checkbox'); + this.secureTransactionIcon = this.merchCard.locator('.secure-transaction-icon'); + this.secureTransactionLabel = this.merchCard.locator('.secure-transaction-label'); + this.footerOutlineButton = this.merchCard.locator('a.con-button.outline'); + this.footerOutlineButton2 = this.merchCard.locator('a.con-button.outline').nth(1); + this.footerBlueButton = this.merchCard.locator('a.con-button.blue').nth(0); + this.footerBlueButton2 = this.merchCard.locator('a.con-button.blue').nth(1); + + // merch-card attributes + this.attributes = { + segmentRibbon: { style: /background-color:\s*#EDCC2D;\s*color:\s*#000000;\s*/ }, + specialOfferRibbon: { style: /background-color:\s* #F68D2E;\s*color:\s*#000000;\s*/ }, + plansRibbon: { style: /background-color:\s*#EDCC2D;\s*color:\s*#000000;\s*/ }, + }; + } +} diff --git a/nala/blocks/merchcard/merchcard.spec.js b/nala/blocks/merchcard/merchcard.spec.js new file mode 100644 index 0000000000..bfc98221f0 --- /dev/null +++ b/nala/blocks/merchcard/merchcard.spec.js @@ -0,0 +1,194 @@ +/* eslint-disable max-len */ + +module.exports = { + FeatureName: 'Merch Card Block', + features: [ + { + tcid: '0', + name: '@Merch-card (Segment)', + path: '/drafts/nala/blocks/merch-card/merch-card-segment', + data: { + title: 'Individuals', + price: 'US$59.99/mo', + strikethroughPrice: 'US$89.99/mo', + description: 'Save over 25% on 20+ apps, including Photoshop, Illustrator, and more. First year only. Ends 27', + link1Text: 'See what\'s included', + link2Text: 'Learn more', + footerOutlineButtonText: 'Learn More', + footerBlueButtonText: 'Save now', + }, + tags: '@merch-card @smoke @regression @milo', + }, + { + tcid: '1', + name: '@Merch-card (Segment) with Badge', + path: '/drafts/nala/blocks/merch-card/merch-card-segment-with-badge', + data: { + title: 'Individuals', + badgeText: 'Best value', + price: 'US$59.99/mo', + strikethroughPrice: 'US$89.99/mo', + description: 'Save over 25% on 20+ apps, including Photoshop, Illustrator, and more. First year only. Ends 27', + link1Text: 'See what\'s included', + link2Text: 'Learn more', + footerOutlineButtonText: 'Learn More', + footerBlueButtonText: 'Save now', + }, + tags: '@merch-card @smoke @regression @milo', + }, + { + tcid: '2', + name: '@Merch-card (special-offers) ', + path: '/drafts/nala/blocks/merch-card/merch-card-special-offers', + data: { + titleH4: 'INDIVIDUALS', + titleH3: 'Save over 30% on Creative Cloud All Apps.', + description1: 'Get 20+ creative apps and save big when you choose a yearly plan instead of a monthly plan.', + description2: 'Create gorgeous images, rich graphics, and incredible art. Save 10% for the first year. Ends Mar 20.', + footerBlueButtonText: 'Save now', + }, + tags: '@merch-card @smoke @regression @milo', + }, + { + tcid: '3', + name: '@Merch-card (special-offers) with badge', + path: '/drafts/nala/blocks/merch-card/merch-card-special-offers-with-badge', + data: { + titleH4: 'INDIVIDUALS', + titleH3: 'Get 10% off Photoshop.', + badgeText: 'LIMITED TIME WEEKLY OFFER', + price: 'US$383.88/yr', + strikethroughPrice: 'US$32.99/mo', + description: 'Create gorgeous images, rich graphics, and incredible art. Save 10% for the first year. Ends Mar 20.', + link1Text: 'See terms', + footerOutlineButtonText: 'Learn More', + footerBlueButtonText: 'Save now', + }, + tags: '@merch-card @smoke @regression @milo', + }, + { + tcid: '4', + name: '@Merch-card (plans)', + path: '/drafts/nala/blocks/merch-card/merch-cards-plans', + data: { + titleH3: 'Creative Cloud All Apps', + titleH5: 'Desktop', + price: 'US$79.99/mo', + description: 'Get 20+ Creative Cloud apps including Photoshop, Illustrator, Adobe Express, Premiere Pro, and Acrobat Pro. (Substance 3D apps are not included.)', + link1Text: 'See plan & pricing details', + footerBlueButtonText: 'Buy now', + }, + tags: '@merch-card @smoke @regression @milo', + }, + { + tcid: '5', + name: '@Merch-card (plans) with badge', + path: '/drafts/nala/blocks/merch-card/merch-card-plans-with-badge', + data: { + titleH3: 'Creative Cloud All Apps', + titleH4: 'Desktop', + badgeText: 'Best value', + price: 'US$79.99/mo', + description: 'Get 20+ Creative Cloud apps including Photoshop, Illustrator, Adobe Express, Premiere Pro, and Acrobat Pro. (Substance 3D apps are not included.)', + link1Text: 'See plan & pricing details', + footerBlueButtonText: 'Buy now', + }, + tags: '@merch-card @smoke @regression @milo', + }, + { + tcid: '6', + name: '@Merch-card (plans, secure)', + path: '/drafts/nala/blocks/merch-card/merch-card-plans-secure', + data: { + titleH3: 'Acrobat', + titleH5: 'Desktop + Mobile', + price: 'US$79.99/mo', + description: 'The complete PDF solution for working anywhere (includes desktop, web, and mobile access).', + link1Text: 'See plan & pricing details', + checkboxLabel: 'Add a 30-day free trial of Adobe Stock.*', + secureLabel: /Secure transaction/i, + footerBlueButton1Text: 'Buy now', + footerBlueButton2Text: 'Buy now', + footerOutlineButtonText: 'Free trial', + }, + tags: '@merch-card @smoke @regression @milo', + }, + { + tcid: '7', + name: '@Merch-card (plans, secure) with badge', + path: '/drafts/nala/blocks/merch-card/merch-cards-plans-secure-with-badge', + data: { + titleH3: 'Creative Cloud All Apps', + titleH5: 'Desktop', + badgeText: 'Best value', + price: 'US$79.99/mo', + description: 'Get 20+ Creative Cloud apps including Photoshop, Illustrator, Adobe Express, Premiere Pro, and Acrobat Pro. (Substance 3D apps are not included.)', + link1Text: 'See plan & pricing details', + checkboxLabel: 'Add a 30-day free trial of Adobe Stock.*', + secureLabel: /Secure transaction/i, + footerBlueButton1Text: /Buy now/i, + footerOutlineButtonText: 'Free trial', + }, + tags: '@merch-card @smoke @regression @milo', + }, + { + tcid: '8', + name: '@Merch-card (catalog)', + path: '/drafts/nala/blocks/merch-card/merch-cards-catalog', + data: { + titleH3: 'Creative Cloud All Apps', + titleH4: 'Desktop', + price: 'US$79.99/mo', + description: 'Get 20+ creative apps including Photoshop, Illustrator, Premiere Pro, Acrobat Pro, and Adobe Express. (Substance 3D apps are not included.)', + link1Text: 'See what’s included', + link2Text: 'Learn more', + footerBlueButton1Text: 'Buy now', + footerOutlineButtonText: 'free trial', + }, + tags: '@merch-card @smoke @regression @milo', + }, + { + tcid: '9', + name: '@Merch-card (catalog) with badge', + path: '/drafts/nala/blocks/merch-card/march-cards-catalog-with-badge', + data: { + titleH3: 'Creative Cloud All Apps', + titleH4: 'Desktop', + badgeText: 'Most popular', + badgeBgColor: '#EDCC2D', + badgeColor: '#000000', + price: 'US$79.99/mo', + description: 'Get 20+ creative apps including Photoshop, Illustrator, Premiere Pro, Acrobat Pro, and Adobe Express. (Substance 3D apps are not included.)', + link1Text: 'See what’s included', + link2Text: 'Learn more', + footerBlueButton1Text: 'Buy now', + footerOutlineButtonText: 'free trial', + }, + tags: '@merch-card @smoke @regression @milo', + }, + { + tcid: '10', + name: '@Merch-card (catalog) with more info and badge', + path: '/drafts/nala/blocks/merch-card/merch-cards-catalog-with-more-info-and-badge', + data: { + titleH3: 'Creative Cloud All Apps', + titleH4: 'Desktop', + badgeText: 'Most popular', + badgeBgColor: '#EDCC2D', + badgeColor: '#000000', + actionMenuListCount: 4, + actionMenuText1: 'Best for', + actionMenuText2: 'Storage', + actionMenuText3: '100 GB of cloud storage', + actionMenuText4: '100 GB of cloud storage', + price: 'US$79.99/mo', + description: 'Get 20+ creative apps including Photoshop, Illustrator, Premiere Pro, Acrobat Pro, and Adobe Express. (Substance 3D apps are not included.)', + link1Text: 'See what’s included', + link2Text: 'Learn more', + footerBlueButton1Text: 'Buy now', + footerOutlineButtonText: 'free trial', + }, + tags: '@merch-card @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/merchcard/merchcard.test.js b/nala/blocks/merchcard/merchcard.test.js new file mode 100644 index 0000000000..65db3f87e0 --- /dev/null +++ b/nala/blocks/merchcard/merchcard.test.js @@ -0,0 +1,393 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console, no-plusplus */ +import { expect, test } from '@playwright/test'; +import { features } from './merchcard.spec.js'; +import MerchCard from './merchcard.pages.js'; + +let merchCard; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Merchcard block test suite', () => { + test.beforeEach(async ({ page, browserName }) => { + merchCard = new MerchCard(page); + if (browserName === 'chromium') { + await page.setExtraHTTPHeaders({ 'sec-ch-ua': '"Chromium";v="123", "Not:A-Brand";v="8"' }); + } + }); + + // Test 0 : Merch Card (Segment) + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + const { data } = features[0]; + + await test.step('step-1: Go to Merch Card feature test page', async () => { + await page.goto(`${baseURL}${features[0].path}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}`); + }); + + await test.step('step-2: Verify Merch Card content/specs', async () => { + await expect(await merchCard.segment).toBeVisible(); + await expect(await merchCard.segmentTitle).toContainText(data.title); + // await expect(await merchCard.price).toContainText(data.price); + // await expect(await merchCard.strikethroughPrice).toContainText(data.strikethroughPrice); + + await expect(await merchCard.segmentDescription1).toContainText(data.description); + await expect(await merchCard.linkText1).toContainText(data.link1Text); + await expect(await merchCard.linkText2).toContainText(data.link2Text); + await expect(await merchCard.footer).toBeVisible(); + await expect(await merchCard.footerOutlineButton).toBeVisible(); + await expect(await merchCard.footerOutlineButton).toContainText(data.footerOutlineButtonText); + await expect(await merchCard.footerBlueButton).toBeVisible(); + await expect(await merchCard.footerBlueButton).toContainText(data.footerBlueButtonText); + }); + }); + + // Test 1 : Merch Card (Segment) with Badge + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + const { data } = features[1]; + + await test.step('step-1: Go to Merch Card feature test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Merch Card with Badge content/specs', async () => { + await expect(await merchCard.segment).toBeVisible(); + await expect(await merchCard.segmentTitle).toContainText(data.title); + + await expect(await merchCard.segmentRibbon).toBeVisible(); + await expect(await merchCard.segmentRibbon).toContainText(data.badgeText); + + // await expect(await merchCard.price).toContainText(data.price); + // await expect(await merchCard.strikethroughPrice).toContainText(data.strikethroughPrice); + + await expect(await merchCard.segmentDescription1).toContainText(data.description); + await expect(await merchCard.linkText1).toContainText(data.link1Text); + await expect(await merchCard.linkText2).toContainText(data.link2Text); + + await expect(await merchCard.footer).toBeVisible(); + await expect(await merchCard.footerOutlineButton).toBeVisible(); + await expect(await merchCard.footerOutlineButton).toContainText(data.footerOutlineButtonText); + + await expect(await merchCard.footerBlueButton).toBeVisible(); + await expect(await merchCard.footerBlueButton).toContainText(data.footerBlueButtonText); + }); + + await test.step('step-3: Verify Merch Card attributes', async () => { + await expect(await merchCard.segmentRibbon).toHaveAttribute('style', merchCard.attributes.segmentRibbon.style); + }); + }); + + // Test 2 : Merch Card (Special Offers) + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + const { data } = features[2]; + + await test.step('step-1: Go to Merch Card feature test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Merch Card special offers content/specs', async () => { + await expect(await merchCard.sepcialOffers).toBeVisible(); + await expect(await merchCard.sepcialOffersImage).toBeVisible(); + + await expect(await merchCard.sepcialOffersTitleH4).toBeVisible(); + await expect(await merchCard.sepcialOffersTitleH4).toContainText(data.titleH4); + await expect(await merchCard.sepcialOffersTitleH3).toContainText(data.titleH3); + + await expect(await merchCard.sepcialOffersDescription1).toContainText(data.description1); + await expect(await merchCard.sepcialOffersDescription2).toContainText(data.description2); + + await expect(await merchCard.footer).toBeVisible(); + await expect(await merchCard.footerBlueButton).toBeVisible(); + await expect(await merchCard.footerBlueButton).toContainText(data.footerBlueButtonText); + }); + }); + + // Test 3 : Merch Card (Special Offers) with badge + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[3].path}${miloLibs}`); + const { data } = features[3]; + + await test.step('step-1: Go to Merch Card feature test page', async () => { + await page.goto(`${baseURL}${features[3].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[3].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Merch Card special offers content/specs', async () => { + await expect(await merchCard.sepcialOffers).toBeVisible(); + await expect(await merchCard.sepcialOffersImage).toBeVisible(); + + await expect(await merchCard.sepcialOffersRibbon).toBeVisible(); + await expect(await merchCard.sepcialOffersRibbon).toContainText(data.badgeText); + + await expect(await merchCard.sepcialOffersTitleH3).toContainText(data.titleH3); + await expect(await merchCard.sepcialOffersTitleH4).toContainText(data.titleH4); + + await expect(await merchCard.sepcialOffersDescription1).toContainText(data.description); + await expect(await merchCard.seeTermsTextLink).toContainText(data.link1Text); + + await expect(await merchCard.footer).toBeVisible(); + await expect(await merchCard.footerOutlineButton).toBeVisible(); + await expect(await merchCard.footerOutlineButton).toContainText(data.footerOutlineButtonText); + + await expect(await merchCard.footerBlueButton).toBeVisible(); + await expect(await merchCard.footerBlueButton).toContainText(data.footerBlueButtonText); + }); + + await test.step('step-3: Verify Merch Card attributes', async () => { + await expect(await merchCard.sepcialOffersRibbon).toHaveAttribute( + 'style', + merchCard.attributes.specialOfferRibbon.style, + ); + }); + }); + + // Test 4 : Merch Card (plans) + test(`${features[4].name},${features[4].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[4].path}${miloLibs}`); + const { data } = features[4]; + + await test.step('step-1: Go to Merch Card feature test page', async () => { + await page.goto(`${baseURL}${features[4].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[4].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Merch Card special offers content/specs', async () => { + await expect(await merchCard.plans).toBeVisible(); + await expect(await merchCard.productIcon).toBeVisible(); + + await expect(await merchCard.plansCardTitleH3).toContainText(data.titleH3); + await expect(await merchCard.plansCardTitleH5).toContainText(data.titleH5); + + // await expect(await merchCard.price).toContainText(data.price); + await expect(await merchCard.plansCardDescription1).toContainText(data.description); + await expect(await merchCard.seePlansTextLink).toContainText(data.link1Text); + + await expect(await merchCard.footer).toBeVisible(); + + await expect(await merchCard.footerBlueButton).toBeVisible(); + await expect(await merchCard.footerBlueButton).toContainText(data.footerBlueButtonText); + }); + }); + + // Test 5 : Merch Card (plans) with badge + test(`${features[5].name},${features[5].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[5].path}${miloLibs}`); + const { data } = features[5]; + + await test.step('step-1: Go to Merch Card feature test page', async () => { + await page.goto(`${baseURL}${features[5].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[5].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Merch Card special offers content/specs', async () => { + await expect(await merchCard.plans).toBeVisible(); + await expect(await merchCard.productIcon).toBeVisible(); + + await expect(await merchCard.plansRibbon).toBeVisible(); + await expect(await merchCard.plansRibbon).toContainText(data.badgeText); + + await expect(await merchCard.plansCardTitleH3).toContainText(data.titleH3); + await expect(await merchCard.plansCardTitleH4).toContainText(data.titleH4); + + // await expect(await merchCard.price).toContainText(data.price); + await expect(await merchCard.plansCardDescription2).toContainText(data.description); + await expect(await merchCard.seePlansTextLink).toContainText(data.link1Text); + + await expect(await merchCard.footer).toBeVisible(); + + await expect(await merchCard.footerBlueButton).toBeVisible(); + await expect(await merchCard.footerBlueButton).toContainText(data.footerBlueButtonText); + }); + }); + + // Test 6 : Merch Card (plans) with secure + test(`${features[6].name},${features[6].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[6].path}${miloLibs}`); + const { data } = features[6]; + + await test.step('step-1: Go to Merch Card feature test page', async () => { + await page.goto(`${baseURL}${features[6].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[6].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Merch Card special offers content/specs', async () => { + await expect(await merchCard.plans).toBeVisible(); + await expect(await merchCard.productIcon).toBeVisible(); + + await expect(await merchCard.plansCardTitleH3).toContainText(data.titleH3); + await expect(await merchCard.plansCardTitleH5).toContainText(data.titleH5); + + // await expect(await merchCard.price).toContainText(data.price); + await expect(await merchCard.plansCardDescription1).toContainText(data.description); + await expect(await merchCard.seePlansTextLink).toContainText(data.link1Text); + + await expect(await merchCard.footer).toBeVisible(); + + await expect(await merchCard.footerBlueButton).toBeVisible(); + await expect(await merchCard.footerBlueButton).toContainText(data.footerBlueButton1Text); + + await expect(await merchCard.secureTransactionLabel).toContainText(data.secureLabel); + }); + }); + + // Test 7 : Merch Card (plans, secure) with badge + test(`${features[7].name},${features[7].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[7].path}${miloLibs}`); + const { data } = features[7]; + + await test.step('step-1: Go to Merch Card feature test page', async () => { + await page.goto(`${baseURL}${features[7].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[7].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Merch Card special offers content/specs', async () => { + await expect(await merchCard.plans).toBeVisible(); + await expect(await merchCard.productIcon).toBeVisible(); + + await expect(await merchCard.plansRibbon).toBeVisible(); + await expect(await merchCard.plansRibbon).toContainText(data.badgeText); + + await expect(await merchCard.plansCardTitleH3).toContainText(data.titleH3); + await expect(await merchCard.plansCardTitleH5).toContainText(data.titleH5); + + // await expect(await merchCard.price).toContainText(data.price); + await expect(await merchCard.plansCardDescription1).toContainText(data.description); + await expect(await merchCard.seePlansTextLink).toContainText(data.link1Text); + + await expect(await merchCard.footer).toBeVisible(); + await expect(await merchCard.footerBlueButton).toBeVisible(); + await expect(await merchCard.footerBlueButton).toContainText(data.footerBlueButton1Text); + + await expect(await merchCard.secureTransactionLabel).toContainText(data.secureLabel); + }); + }); + + // Test 8 : Merch Card (catalog) + test(`${features[8].name},${features[8].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[8].path}${miloLibs}`); + const { data } = features[8]; + + await test.step('step-1: Go to Merch Card feature test page', async () => { + await page.goto(`${baseURL}${features[8].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[8].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Merch Card catalog content/specs', async () => { + await expect(await merchCard.catalog).toBeVisible(); + await expect(await merchCard.catalogCardTitleH3).toContainText(data.titleH3); + await expect(await merchCard.catalogCardTitleH4).toContainText(data.titleH4); + + // await expect(await merchCard.price).toContainText(data.price); + + await expect(await merchCard.catalogCardDescription2).toContainText(data.description); + await expect(await merchCard.seeWhatsIncludedTextLink).toContainText(data.link1Text); + await expect(await merchCard.learnMoreTextLink).toContainText(data.link2Text); + + await expect(await merchCard.footer).toBeVisible(); + + await expect(await merchCard.footerBlueButton).toBeVisible(); + await expect(await merchCard.footerBlueButton).toContainText(data.footerBlueButton1Text); + + await expect(await merchCard.footerOutlineButton).toBeVisible(); + await expect(await merchCard.footerOutlineButton).toContainText(data.footerOutlineButtonText); + }); + }); + + // Test 9 : Merch Card (catalog) with badge + test(`${features[9].name},${features[9].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[9].path}${miloLibs}`); + const { data } = features[9]; + + await test.step('step-1: Go to Merch Card feature test page', async () => { + await page.goto(`${baseURL}${features[9].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[9].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Merch Card catalog with badge content/specs', async () => { + await expect(await merchCard.catalog).toBeVisible(); + + await expect(await merchCard.catalog).toHaveAttribute('badge-background-color', data.badgeBgColor); + await expect(await merchCard.catalog).toHaveAttribute('badge-color', data.badgeColor); + await expect(await merchCard.catalog).toHaveAttribute('badge-text', data.badgeText); + + await expect(await merchCard.catalogCardTitleH3).toContainText(data.titleH3); + await expect(await merchCard.catalogCardTitleH4).toContainText(data.titleH4); + + // await expect(await merchCard.price).toContainText(data.price); + + await expect(await merchCard.catalogCardDescription2).toContainText(data.description); + await expect(await merchCard.seeWhatsIncludedTextLink).toContainText(data.link1Text); + await expect(await merchCard.learnMoreTextLink).toContainText(data.link2Text); + + await expect(await merchCard.footer).toBeVisible(); + + await expect(await merchCard.footerBlueButton).toBeVisible(); + await expect(await merchCard.footerBlueButton).toContainText(data.footerBlueButton1Text); + + await expect(await merchCard.footerOutlineButton).toBeVisible(); + await expect(await merchCard.footerOutlineButton).toContainText(data.footerOutlineButtonText); + }); + }); + + // Test 10 : Merch Card (catalog) with more info and badge + test(`${features[10].name},${features[10].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[10].path}${miloLibs}`); + const { data } = features[10]; + + await test.step('step-1: Go to Merch Card feature test page', async () => { + await page.goto(`${baseURL}${features[10].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[10].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Merch Card catalog with badge content/specs', async () => { + await expect(await merchCard.catalog).toBeVisible(); + + await expect(await merchCard.catalog).toHaveAttribute('badge-background-color', data.badgeBgColor); + await expect(await merchCard.catalog).toHaveAttribute('badge-color', data.badgeColor); + await expect(await merchCard.catalog).toHaveAttribute('badge-text', data.badgeText); + + await expect(await merchCard.catalogCardTitleH3).toContainText(data.titleH3); + await expect(await merchCard.catalogCardTitleH4).toContainText(data.titleH4); + + // await expect(await merchCard.price).toContainText(data.price); + + await expect(await merchCard.catalogCardDescription2).toContainText(data.description); + await expect(await merchCard.seeWhatsIncludedTextLink).toContainText(data.link1Text); + await expect(await merchCard.learnMoreTextLink).toContainText(data.link2Text); + + await expect(await merchCard.footer).toBeVisible(); + + await expect(await merchCard.footerBlueButton).toBeVisible(); + await expect(await merchCard.footerBlueButton).toContainText(data.footerBlueButton1Text); + + await expect(await merchCard.footerOutlineButton).toBeVisible(); + await expect(await merchCard.footerOutlineButton).toContainText(data.footerOutlineButtonText); + }); + + await test.step('step-3: click more info link and verify action menu list', async () => { + await merchCard.catalog.hover(); + await merchCard.catalog.click(); + await page.waitForTimeout(1000); + + await expect(await merchCard.catalogActionMenuList).toHaveCount(data.actionMenuListCount); + await expect(await merchCard.catalogActionMenuPText1).toContainText(data.actionMenuText1); + await expect(await merchCard.catalogActionMenuPText2).toContainText(data.actionMenuText2); + await expect(await merchCard.catalogActionMenuPText3).toContainText(data.actionMenuText3); + }); + }); +}); diff --git a/nala/blocks/modal/modal.page.js b/nala/blocks/modal/modal.page.js new file mode 100644 index 0000000000..4fc19bff09 --- /dev/null +++ b/nala/blocks/modal/modal.page.js @@ -0,0 +1,62 @@ +export default class Modal { + constructor(page) { + this.page = page; + // modal locators + this.dialog = this.page.locator('.dialog-modal'); + this.modal = this.page.locator('.dialog-modal'); + this.fragment = this.modal.locator('.fragment'); + this.headingXL = this.page.locator('.heading-xl'); + this.bodyM = this.page.locator('.body-m').nth(2); + this.modalCloseButton = this.modal.locator('.dialog-close'); + this.dialogCloseButton = this.modal.locator('.dialog-close').nth(0); + this.marqueeLight = this.dialog.locator('.marquee.light'); + this.modelSelector = '.dialog-modal'; + + // text block + this.textBlock = this.modal.locator('.text').nth(0); + this.textBlockHeading = this.textBlock.locator('h2'); + this.textBlockBodyM = this.textBlock.locator('.body-m'); + + // media block + this.mediaBlock = this.modal.locator('.media').nth(0); + this.mediaBlockdetailM = this.mediaBlock.locator('.detail-m'); + this.mediaBlockTextHeading = this.mediaBlock.locator('h2'); + this.mediaBlockTextBodyS = this.mediaBlock.locator('.body-s').first(); + + // video block + this.video = this.modal.locator('video').nth(0); + + // modal contents attributes + this.attributes = { + 'modal-link': { class: 'modal link-block ' }, + 'video.inline': { + playsinline: '', + autoplay: '', + loop: '', + muted: '', + }, + }; + } + + /** + * Gets the modal link based on the modal id. + * Waits for the link to be visible before returning the locator. + * @param {Object} data - The data object containing modalId. + * @param {number} [timeout=3000] - Optional timeout for waiting. + * @returns {Promise} - The locator for the modal link. + * @throws Will throw an error if the link is not found within the timeout. + */ + async getModalLink(modalId, timeout = 1000) { + if (!modalId) { + throw new Error('Invalid data, "modalId" property is required.'); + } + const selector = `a[href="#${modalId}"]`; + const modalLink = this.page.locator(selector); + try { + await modalLink.waitFor({ state: 'visible', timeout }); + return modalLink; + } catch (error) { + throw new Error(`The modal link with selector "${selector}" could not be found within ${timeout}ms.`); + } + } +} diff --git a/nala/blocks/modal/modal.spec.js b/nala/blocks/modal/modal.spec.js new file mode 100644 index 0000000000..432e754edb --- /dev/null +++ b/nala/blocks/modal/modal.spec.js @@ -0,0 +1,46 @@ +module.exports = { + FeatureName: 'Modal Block', + features: [ + { + tcid: '0', + name: '@Modal Text', + path: '/drafts/nala/blocks/modal/modal-text-intro', + data: { + modalId: 'modal-text-intro', + fragment: 'text', + contentType: 'text (intro)', + h2Text: 'Text (intro)', + bodyText: 'Body M Regular Lorem ipsum dolor sit amet', + }, + tags: '@modal @smoke @regression @milo', + }, + { + tcid: '1', + name: '@Modal Media', + path: '/drafts/nala/blocks/modal/modal-media', + data: { + modalId: 'modal-media', + detailText: 'Detail M 12/15', + fragment: 'media', + contentType: 'media', + h2Text: 'Heading M 24/30 Media', + bodyText: 'Body S 16/24 Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed', + }, + tags: '@modal @smoke @regression @milo', + }, + { + tcid: '2', + name: '@Modal Autoplay Video', + path: '/drafts/nala/blocks/modal/modal-autoplay-video', + data: { + modalId: 'modal-video-autoplay', + detailText: 'Detail M 12/15', + fragment: 'media', + contentType: 'media', + h2Text: 'Heading M 24/30 Media', + bodyText: 'Body S 16/24 Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed', + }, + tags: '@modal @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/modal/modal.test.js b/nala/blocks/modal/modal.test.js new file mode 100644 index 0000000000..e356ff7c70 --- /dev/null +++ b/nala/blocks/modal/modal.test.js @@ -0,0 +1,115 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import WebUtil from '../../libs/webutil.js'; +import { features } from './modal.spec.js'; +import ModalBlock from './modal.page.js'; + +let modal; +let webUtil; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Modal feature test suite', () => { + test.beforeEach(async ({ page }) => { + modal = new ModalBlock(page); + webUtil = new WebUtil(page); + }); + + // Test 0 : Modal with Text block + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + + await test.step('step-1: Go to Modal feature test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Modal text fragment content/specs', async () => { + const { data } = features[0]; + const modalLink = await modal.getModalLink(data.modalId); + await expect(await modalLink).toBeVisible(); + await expect(await modalLink).toHaveAttribute('class', modal.attributes['modal-link'].class); + + // click the modal link + await modalLink.click(); + await expect(await modal.dialog).toBeVisible(); + + await expect(await modal.textBlock).toBeVisible(); + await expect(await modal.textBlockHeading).toContainText(data.h2Text); + await expect(await modal.textBlockBodyM).toContainText(data.bodyText); + + expect(await WebUtil.isModalInViewport(modal.page, modal.modalSelector)).toBeTruthy(); + + // click the modal close button + await expect(await modal.dialogCloseButton).toBeVisible(); + await modal.dialogCloseButton.click(); + }); + }); + + // Test 1 : Modal with Media block + test(`${features[1].name}, ${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + + await test.step('step-1: Go to Modal feature test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Modal media fragement content/specs', async () => { + const { data } = features[1]; + // expect(await modal.verifyModal(modalData)).toBeTruthy(); + + const modalLink = await modal.getModalLink(data.modalId); + await expect(await modalLink).toBeVisible(); + await expect(await modalLink).toHaveAttribute('class', modal.attributes['modal-link'].class); + + // click the modal link + await modalLink.click(); + await expect(await modal.dialog).toBeVisible(); + + await expect(await modal.mediaBlock).toBeVisible(); + await expect(await modal.mediaBlockdetailM).toContainText(data.detailText); + await expect(await modal.mediaBlockTextHeading).toContainText(data.h2Text); + await expect(await modal.mediaBlockTextBodyS).toContainText(data.bodyText); + + expect(await WebUtil.isModalInViewport(modal.page, modal.modalSelector)).toBeTruthy(); + + // close the modal using escape key press + await expect(await modal.dialogCloseButton).toBeVisible(); + await modal.page.keyboard.press('Escape'); + }); + }); + + // Test 2 : Modal with Video Autoplay + test(`${features[2].name}, ${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + + await test.step('step-1: Go to Modal feature test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Modal media fragement content/specs', async () => { + const { data } = features[2]; + + const modalLink = await modal.getModalLink(data.modalId); + await expect(await modalLink).toBeVisible(); + await expect(await modalLink).toHaveAttribute('class', modal.attributes['modal-link'].class); + + // click the modal link and verify video autoplay + await modalLink.click(); + await expect(await modal.dialog).toBeVisible(); + expect(await WebUtil.isModalInViewport(modal.page, modal.modalSelector)).toBeTruthy(); + + await expect(await modal.video).toBeVisible(); + expect(await webUtil.verifyAttributes(await modal.video, modal.attributes['video.inline'])).toBeTruthy(); + + // close the modal using escape key press + await expect(await modal.dialogCloseButton).toBeVisible(); + await modal.page.keyboard.press('Escape'); + }); + }); +}); diff --git a/nala/blocks/quote/quote.page.js b/nala/blocks/quote/quote.page.js new file mode 100644 index 0000000000..581e5ac442 --- /dev/null +++ b/nala/blocks/quote/quote.page.js @@ -0,0 +1,64 @@ +export default class Quote { + constructor(page, nth = 0) { + this.page = page; + // quote locators + this.quote = this.page.locator('.quote').nth(nth); + this.quoteImage = this.quote.locator('.quote-image'); + this.quoteCopy = this.quote.locator('p.quote-copy'); + this.quoteFigCaption = this.quote.locator('p.figcaption'); + this.quoteFigCaptionCite = this.quote.locator('cite p'); + this.sectionDark = this.page.locator('.section.dark'); + + // quote blocks css + this.cssProperties = { + quote: { + 'text-align': 'center', + margin: /^0px.*/, + }, + + 'quote-contained': { + 'text-align': 'center', + margin: /^0px.*/, + }, + + 'quote-align-right': { + 'text-align': 'right', + margin: /^0px.*/, + }, + + 'quote-copy': { + 'font-size': '24px', + 'font-weight': 'bold', + }, + + 'quote-inline-figure': { + display: 'flex', + 'align-content': 'center', + flex: '1 0 40%', + margin: '0px', + 'justify-content': 'center', + }, + + 'quote-inline-image': { + height: '200px', + 'max-height': '200px', + }, + + figcaption: { + 'font-size': '16px', + 'font-weight': 'bold', + }, + }; + + // quote blocks attributes + this.attProperties = { + quote: { class: 'quote con-block' }, + 'quote-contained': { class: 'quote contained con-block' }, + 'quote-inline': { class: 'quote inline contained con-block' }, + 'quote-borders': { class: 'quote borders contained con-block' }, + 'quote-align-right': { class: 'quote contained align-right con-block' }, + 'quote-xl-spacing': { class: 'quote contained xl-spacing con-block' }, + 'section-dark': { style: 'background: rgb(102, 102, 102);' }, + }; + } +} diff --git a/nala/blocks/quote/quote.spec.js b/nala/blocks/quote/quote.spec.js new file mode 100644 index 0000000000..ac1f36019f --- /dev/null +++ b/nala/blocks/quote/quote.spec.js @@ -0,0 +1,73 @@ +/* eslint-disable max-len */ + +module.exports = { + FeatureName: 'Quote Block', + features: [ + { + tcid: '0', + name: '@Quote ', + path: '/drafts/nala/blocks/quote/quote', + data: { + quoteCopy: '3D is a crucial part of how we explore the brand in a digital workflow', + figCaption: 'Benny Lee', + cite: 'Global Manager of Experiential Design, Coca-Cola Company', + }, + tags: '@Quote @smoke @regression @milo', + }, + { + tcid: '1', + name: '@Quote (contained)', + path: '/drafts/nala/blocks/quote/quote-contained', + data: { + quoteCopy: '3D is a crucial part of how we explore the brand in a digital workflow', + figCaption: 'Benny Lee', + cite: 'Global Manager of Experiential Design, Coca-Cola Company', + }, + tags: '@Quote @quote-contained @smoke @regression @milo', + }, + { + tcid: '2', + name: '@Quote (inline,contained)', + path: '/drafts/nala/blocks/quote/quote-inline-contained', + data: { + quoteCopy: 'We are the guardians of a 135-year-old brand.', + figCaption: 'Rapha Abreu', + cite: 'Global Vice President of Design, Coca-Cola Company', + }, + tags: '@Quote @quote-inline @smoke @regression @milo,', + }, + { + tcid: '3', + name: '@Quote (borders,contained)', + path: '/drafts/nala/blocks/quote/quote-borders-contained', + data: { + quoteCopy: 'This was our opportunity to be one of the first teams to delve into Adobe Experience Platform', + figCaption: 'Ron Nagy', + cite: 'Sr. Evangelist, Adobe@Adobe', + }, + tags: '@Quote @quote-borders @smoke @regression @milo,', + }, + { + tcid: '4', + name: '@Quote (contained, align-right)', + path: '/drafts/nala/blocks/quote/quote-contained-align-right', + data: { + quoteCopy: '“This was our opportunity to be one of the first teams to delve into Adobe Experience Platform, and we wanted to show people just how powerful it can be.”', + figCaption: 'Ron Nagy', + cite: 'Sr. Evangelist, Adobe@Adobe', + }, + tags: '@Quote @quote-align-right @smoke @regression @milo,', + }, + { + tcid: '5', + name: '@Quote (xl-spaced)', + path: '/drafts/nala/blocks/quote/quote-xl-spaced', + data: { + quoteCopy: 'This was our opportunity to be one of the first teams to delve into Adobe Experience Platform', + figCaption: 'Ron Nagy', + cite: 'Sr. Evangelist, Adobe@Adobe', + }, + tags: '@Quote @quote-xl-spaced @smoke @regression @milo,', + }, + ], +}; diff --git a/nala/blocks/quote/quote.test.js b/nala/blocks/quote/quote.test.js new file mode 100644 index 0000000000..c6bf4e8f7a --- /dev/null +++ b/nala/blocks/quote/quote.test.js @@ -0,0 +1,152 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import WebUtil from '../../libs/webutil.js'; +import { features } from './quote.spec.js'; +import QuoteBlock from './quote.page.js'; + +let quote; +let webUtil; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Quote Block test suite', () => { + test.beforeEach(async ({ page }) => { + webUtil = new WebUtil(page); + quote = new QuoteBlock(page); + }); + + // Test 0 : Quote default block + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + const { data } = features[0]; + + await test.step('step-1: Go to Quote block test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Quote block content/specs', async () => { + await expect(await quote.quoteImage).toBeVisible(); + await expect(await quote.quoteCopy).toContainText(data.quoteCopy); + await expect(await quote.quoteFigCaption).toContainText(data.figCaption); + await expect(await quote.quoteFigCaptionCite).toContainText(data.cite); + + expect(await webUtil.verifyAttributes(await quote.quote, quote.attProperties.quote)).toBeTruthy(); + expect(await webUtil.verifyCSS(await quote.quote, quote.cssProperties.quote)).toBeTruthy(); + }); + }); + + // Test 1 : quote (contained) + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + const { data } = features[1]; + + await test.step('step-1: Go to Quote block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Quote (contained) block content/specs', async () => { + await expect(await quote.quoteImage).toBeVisible(); + await expect(await quote.quoteCopy).toContainText(data.quoteCopy); + await expect(await quote.quoteFigCaption).toContainText(data.figCaption); + await expect(await quote.quoteFigCaptionCite).toContainText(data.cite); + + expect(await webUtil.verifyAttributes(await quote.quote, quote.attProperties['quote-contained'])).toBeTruthy(); + expect(await webUtil.verifyCSS(await quote.quote, quote.cssProperties['quote-contained'])).toBeTruthy(); + }); + }); + + // Test 2 : Quote (inline,contained) + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + const { data } = features[2]; + + await test.step('step-1: Go to Quote (inline) block test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Quote (inline) block content/specs', async () => { + await expect(await quote.quoteImage).toBeVisible(); + await expect(await quote.quoteCopy).toContainText(data.quoteCopy); + await expect(await quote.quoteFigCaption).toContainText(data.figCaption); + await expect(await quote.quoteFigCaptionCite).toContainText(data.cite); + + expect(await webUtil.verifyAttributes(await quote.quote, quote.attProperties['quote-inline'])).toBeTruthy(); + expect(await webUtil.verifyCSS(await quote.quote, quote.cssProperties.quote)).toBeTruthy(); + expect(await webUtil.verifyCSS(await quote.quoteImage, quote.cssProperties['quote-inline-figure'])).toBeTruthy(); + }); + }); + + // Test 3 : quote (borders) + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL }) => { + console.info(`[MiloInfo] Checking page: ${baseURL}${features[3].path}${miloLibs}`); + const { data } = features[3]; + + await test.step('step-1: Go to Quote (borders) block test page', async () => { + await page.goto(`${baseURL}${features[3].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[3].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Quote (borders) block content/specs', async () => { + await expect(await quote.quoteImage).not.toBeVisible(); + await expect(await quote.quoteCopy).toContainText(data.quoteCopy); + await expect(await quote.quoteFigCaption).toContainText(data.figCaption); + await expect(await quote.quoteFigCaptionCite).toContainText(data.cite); + + expect(await webUtil.verifyAttributes(await quote.quote, quote.attProperties['quote-borders'])).toBeTruthy(); + expect(await webUtil.verifyCSS(await quote.quote, quote.cssProperties.quote)).toBeTruthy(); + }); + }); + + // Test 4 : quote (align-right) + test(`${features[4].name},${features[4].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[4].path}${miloLibs}`); + const { data } = features[4]; + + await test.step('step-1: Go to Quote (align-right) block test page', async () => { + await page.goto(`${baseURL}${features[4].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[4].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Quote (align-right) block content/specs', async () => { + await expect(await quote.quoteImage).toBeVisible(); + await expect(await quote.quoteCopy).toContainText(data.quoteCopy); + await expect(await quote.quoteFigCaption).toContainText(data.figCaption); + await expect(await quote.quoteFigCaptionCite).toContainText(data.cite); + + expect(await webUtil.verifyAttributes(await quote.quote, quote.attProperties['quote-align-right'])).toBeTruthy(); + expect(await webUtil.verifyCSS(await quote.quote, quote.cssProperties['quote-align-right'])).toBeTruthy(); + }); + }); + + // Test 5 : quote (xl-spaced) + test(`${features[5].name},${features[5].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[5].path}${miloLibs}`); + const { data } = features[5]; + + await test.step('step-1: Go to Quote (xl-spaced) block test page', async () => { + await page.goto(`${baseURL}${features[5].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[5].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Quote (xl-spaced) block content/specs', async () => { + await expect(await quote.sectionDark).toBeVisible(); + await expect(await quote.quoteImage).not.toBeVisible(); + await expect(await quote.quoteCopy).toContainText(data.quoteCopy); + await expect(await quote.quoteFigCaption).toContainText(data.figCaption); + await expect(await quote.quoteFigCaptionCite).toContainText(data.cite); + + expect(await webUtil.verifyAttributes(await quote.sectionDark, quote.attProperties['section-dark'])).toBeTruthy(); + expect(await webUtil.verifyAttributes(await quote.quote, quote.attProperties['quote-xl-spacing'])).toBeTruthy(); + expect(await webUtil.verifyCSS(await quote.quote, quote.cssProperties.quote)).toBeTruthy(); + }); + }); +}); diff --git a/nala/blocks/review/review.page.js b/nala/blocks/review/review.page.js new file mode 100644 index 0000000000..7fb93650c3 --- /dev/null +++ b/nala/blocks/review/review.page.js @@ -0,0 +1,89 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect } from '@playwright/test'; + +export default class Review { + constructor(page) { + this.page = page; + // review block locators + this.review = this.page.locator('.review'); + this.reviewTitle = this.review.locator('.hlx-reviewTitle'); + this.reviewFieldSet = this.review.locator('form input'); + this.reviewTextArea = this.review.locator('#rating-comments'); + this.sendButton = this.review.locator('input[type="submit"]'); + } + + /** + * Verifies milo review block . + * @param {string} data - data required to verify review block. + * @returns {Promise} - A Promise that resolves to true if the verification + * is successful, or false if an error occurs. + */ + async verifyReview(data) { + try { + // verify review blcok + await expect(await this.review).toBeVisible(); + await expect(await this.reviewTitle).toContainText(data.reviewTitle); + await expect(await this.reviewFieldSet).toHaveCount(data.reviewFields); + + // Expected values review checkboxes + const expectedValues = [ + { tooltip: 'Poor', ariaLabel: 'Poor 1 Star', value: '1' }, + { tooltip: 'Below Average', ariaLabel: 'Below Average 2 Star', value: '2' }, + { tooltip: 'Good', ariaLabel: 'Good 3 Star', value: '3' }, + { tooltip: 'Very Good', ariaLabel: 'Very Good 4 Star', value: '4' }, + { tooltip: 'Outstanding', ariaLabel: 'Outstanding 5 Star', value: '5' }, + ]; + const reviewCheckBoxes = await this.reviewFieldSet.all(); + const checkBoxes = await Promise.all(reviewCheckBoxes.map(async (el) => el)); + // eslint-disable-next-line no-restricted-syntax + for (const checkbox of checkBoxes) { + const tooltip = await checkbox.getAttribute('data-tooltip'); + const ariaLabel = await checkbox.getAttribute('aria-label'); + const value = await checkbox.getAttribute('value'); + // Find the matching expected value + const expectedValue = expectedValues.find((expected) => expected.tooltip === tooltip + && expected.ariaLabel === ariaLabel + && expected.value === value); + // Verify the expected value + if (!expectedValue) { + console.log('Attributes and values are incorrect'); + return false; + } + } + return true; + } catch (error) { + console.error(`Error review block: ${error}`); + return false; + } + } + + /** + * Submits the review rating / form. + * @param {string} checkboxValue - The value of the checkbox to be selected. + * @param {string} textareaValue - The value to be entered in the text area. + * @returns {Promise} - A Promise that resolves to true if the submission is successful, + * or false if an error occurs. + */ + async submitReview(data) { + try { + // Select the n-th rating checkbox + const checkbox = await this.reviewFieldSet.nth(data.rating); + + // if the rating less than 3 then text area field is visible + if (data.rating < 3) { + await checkbox.check(); + await expect(await this.reviewTextArea).toBeVisible(); + await this.reviewTextArea.fill(data.reviewComment); + + // Click the send button + await this.sendButton.click(); + return true; + } + await checkbox.check(); + return true; + } catch (error) { + console.error(`Error submitting the review: ${error}`); + return false; + } + } +} diff --git a/nala/blocks/review/review.spec.js b/nala/blocks/review/review.spec.js new file mode 100644 index 0000000000..575f2e0ae1 --- /dev/null +++ b/nala/blocks/review/review.spec.js @@ -0,0 +1,31 @@ +module.exports = { + FeatureName: 'Review', + features: [ + { + tcid: '0', + name: '@Review low ', + path: '/drafts/nala/blocks/review/review', + data: { + reviewTitle: 'Rate your Experience', + reviewFields: 5, + rating: 4, + reviewComment: 'This is great', + + }, + tags: '@Review @smoke @regression @milo', + }, + { + tcid: '1', + name: '@Review low', + path: '/drafts/nala/blocks/review/review', + data: { + reviewTitle: 'Rate your Experience', + reviewFields: 5, + rating: 2, + reviewComment: 'This is great', + + }, + tags: '@Review @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/review/review.test.js b/nala/blocks/review/review.test.js new file mode 100644 index 0000000000..ac94cc7a3c --- /dev/null +++ b/nala/blocks/review/review.test.js @@ -0,0 +1,49 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import { features } from './review.spec.js'; +import ReviewBlock from './review.page.js'; + +let review; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Review Block test suite', () => { + test.beforeEach(async ({ page, browser }) => { + // review block requires clearing cookies + const context = await browser.newContext(); + await context.clearCookies(); + review = new ReviewBlock(page); + }); + + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + + await test.step('step-1: Go to review feature test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify review block and submit the review < 3', async () => { + const { data } = features[0]; + expect(await review.verifyReview(data)).toBeTruthy(); + expect(await review.submitReview(data)).toBeTruthy(); + }); + }); + + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + + await test.step('step-1: Go to review block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify review block and submit the review > 3', async () => { + const { data } = features[1]; + expect(await review.verifyReview(data)).toBeTruthy(); + expect(await review.submitReview(data)).toBeTruthy(); + }); + }); +}); diff --git a/nala/blocks/table/table.page.js b/nala/blocks/table/table.page.js new file mode 100644 index 0000000000..03c25a2467 --- /dev/null +++ b/nala/blocks/table/table.page.js @@ -0,0 +1,75 @@ +/* eslint-disable no-return-await */ +export default class Table { + constructor(page, nth = 0) { + this.page = page; + // tabel locators + this.table = this.page.locator('.table').nth(nth); + this.highlightTable = this.page.locator('.table.highlight').nth(nth); + this.stickyTable = this.page.locator('.table.sticky').nth(nth); + this.collapseStickyTable = this.page.locator('.table.highlight.collapse.sticky').nth(nth); + this.merchTable = this.page.locator('.table.merch').nth(nth); + this.merchHighlightStickyTable = this.page.locator('.table.merch.highlight.sticky').nth(nth); + + this.highlightRow = this.table.locator('.row-highlight'); + this.headingRow = this.table.locator('.row-heading'); + this.stickyRow = this.table.locator('.row-heading'); + + this.headingRowColumns = this.headingRow.locator('.col'); + this.rows = this.table.locator('.row'); + this.sectionRows = this.table.locator('.section-row'); + } + + async getHighlightRowColumnTitle(colIndex) { + return await this.highlightRow.locator('.col-highlight').nth(colIndex); + } + + async getHeaderColumnTitle(colIndex) { + const headerColumn = await this.headingRow.locator(`.col-${colIndex}`); + return headerColumn.locator('.tracking-header'); + } + + async getHeaderColumnPricing(colIndex) { + const headerColumn = await this.headingRow.locator(`.col-${colIndex}`); + return headerColumn.locator('.pricing'); + } + + async getHeaderColumnImg(colIndex) { + const headerColumn = await this.headingRow.locator(`.col-${colIndex}`); + return headerColumn.locator('img'); + } + + async getHeaderColumnAdditionalText(colIndex) { + const headerColumn = await this.headingRow.locator(`.col-${colIndex}`); + return headerColumn.locator('p').nth(3); + } + + async getHeaderColumnOutlineButton(colIndex) { + const headerColumn = await this.headingRow.locator(`.col-${colIndex}`); + return headerColumn.locator('.con-button.outline'); + } + + async getHeaderColumnBlueButton(colIndex) { + const headerColumn = await this.headingRow.locator(`.col-${colIndex}`); + return headerColumn.locator('.con-button.blue'); + } + + async getSectionRowTitle(index) { + const sectionRow = await this.table.locator('.section-row').nth(index); + return sectionRow.locator('.section-row-title'); + } + + async getSectionRowMerchContent(index) { + const sectionRow = await this.table.locator('.section-row').nth(index); + return sectionRow.locator('.col-merch-content').nth(0); + } + + async getSectionRowMerchContentImg(index) { + const sectionRow = await this.table.locator('.section-row').nth(index); + return sectionRow.locator('.col-merch-content img'); + } + + async getSectionRowCell(rowIndex, colIndex) { + const sectionRow = await this.table.locator('.section-row').nth(rowIndex); + return sectionRow.locator(`.col-${colIndex}`); + } +} diff --git a/nala/blocks/table/table.spec.js b/nala/blocks/table/table.spec.js new file mode 100644 index 0000000000..8ac863fbcf --- /dev/null +++ b/nala/blocks/table/table.spec.js @@ -0,0 +1,121 @@ +module.exports = { + FeatureName: 'Table Block', + features: [ + { + tcid: '0', + name: '@Table (default)', + path: '/drafts/nala/blocks/table/table', + data: { + rowsCount: 9, + headerRowColCount: 5, + sectionRowCount: 8, + headerCell2: { + heading: 'Heading Title-2', + pricingText: 'Pricing-2', + outlineButtonText: 'Free trial', + blueButtonText: 'Buy now', + }, + sectionRow2: { + sectionRowTitle: 'Row-1.1, Title', + cell22: 'Content', + }, + }, + tags: '@table @smoke @regression @milo', + }, + { + tcid: '1', + name: '@Table (highlight)', + path: '/drafts/nala/blocks/table/table-hightlight', + data: { + rowsCount: 10, + headerRowColCount: 5, + sectionRowCount: 8, + hightlightRow: { + cell12: 'Highlight-2', + cell13: 'Highlight-3', + cell14: 'Highlight-4', + }, + headerCell3: { + heading: 'Heading Title-3', + pricingText: 'Pricing-3', + outlineButtonText: 'Free trial', + blueButtonText: 'Buy now', + }, + sectionRow2: { + sectionRowTitle: 'Row-1.1, Title', + cell22: 'Content', + }, + }, + tags: '@table @smoke @regression @milo', + }, + { + tcid: '2', + name: '@Table (sticky)', + path: '/drafts/nala/blocks/table/table-sticky', + data: { + rowsCount: 9, + headerRowColCount: 5, + sectionRowCount: 8, + headerCell4: { + heading: 'Heading Title-4', + pricingText: 'Pricing-4', + outlineButtonText: 'Free trial', + blueButtonText: 'Buy now', + }, + sectionRow2: { + sectionRowTitle: 'Row-1.1, Title', + cell22: 'Content', + }, + }, + tags: '@table @smoke @regression @milo', + }, + { + tcid: '3', + name: '@Table (highlight, collapse, sticky)', + path: '/drafts/nala/blocks/table/table-highlight-collapse-sticky', + data: { + rowsCount: 10, + headerRowColCount: 5, + sectionRowCount: 8, + hightlightRow: { + cell12: 'Highlight-2', + cell13: 'Highlight-3', + cell14: 'Highlight-4', + }, + headerCell5: { + heading: 'Heading Title-5', + pricingText: 'Pricing-5', + outlineButtonText: 'Free trial', + blueButtonText: 'Buy now', + }, + sectionRow2: { + sectionRowTitle: 'Row-1.1, Title', + cell22: 'Content', + }, + }, + tags: '@table @smoke @regression @milo', + }, + { + tcid: '4', + name: '@Table (merch)', + path: '/drafts/nala/blocks/table/table-merch', + data: { + rowsCount: 9, + headerRowColCount: 3, + sectionRowCount: 8, + headerCell1: { + heading: 'Heading Title-1', + pricingText: 'Pricing-1', + AdditionalText: 'Additional Text-1', + outlineButtonText: 'Free trial', + blueButtonText: 'Buy now', + }, + sectionRow2: { + merchContent: 'Section Content-1.1', + image: 'yes', + }, + }, + tags: '@table @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/table/table.test.js b/nala/blocks/table/table.test.js new file mode 100644 index 0000000000..630c8af165 --- /dev/null +++ b/nala/blocks/table/table.test.js @@ -0,0 +1,196 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import { features } from './table.spec.js'; +import TableBlock from './table.page.js'; + +let table; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Table block feature test suite', () => { + test.beforeEach(async ({ page }) => { + table = new TableBlock(page); + }); + + // Test 0 : Table block (default) + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + const { data } = features[0]; + + await test.step('step-1: Go to Table block test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify table content/specs', async () => { + await expect(await table.table).toBeVisible(); + await expect(await table.rows).toHaveCount(data.rowsCount); + await expect(await table.headingRowColumns).toHaveCount(data.headerRowColCount); + await expect(await table.sectionRows).toHaveCount(data.sectionRowCount); + + // verify header row cell + const headerCell = data.headerCell2; + await expect(await table.getHeaderColumnTitle(2)).toContainText(headerCell.heading); + await expect(await table.getHeaderColumnPricing(2)).toContainText(headerCell.pricingText); + await expect(await table.getHeaderColumnOutlineButton(2)).toContainText(headerCell.outlineButtonText); + await expect(await table.getHeaderColumnBlueButton(2)).toContainText(headerCell.blueButtonText); + + // verify section row cell + const sectionCell = data.sectionRow2; + await expect(await table.getSectionRowTitle(2)).toContainText(sectionCell.sectionRowTitle); + await expect(await table.getSectionRowCell(2, 2)).toContainText(sectionCell.cell22); + }); + }); + + // Test 1 : Table (highlight) + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + const { data } = features[1]; + + await test.step('step-1: Go to Table block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify table content/specs', async () => { + await expect(await table.highlightTable).toBeVisible(); + await expect(await table.highlightRow).toBeVisible(); + + await expect(await table.rows).toHaveCount(data.rowsCount); + await expect(await table.headingRowColumns).toHaveCount(data.headerRowColCount); + await expect(await table.sectionRows).toHaveCount(data.sectionRowCount); + + // verify highlighter row + const highlighter = data.hightlightRow; + await expect(await table.getHighlightRowColumnTitle(1)).toContainText(highlighter.cell12); + await expect(await table.getHighlightRowColumnTitle(2)).toContainText(highlighter.cell13); + await expect(await table.getHighlightRowColumnTitle(3)).toContainText(highlighter.cell14); + + // verify header row cell + const headerCell = data.headerCell3; + await expect(await table.getHeaderColumnTitle(3)).toContainText(headerCell.heading); + await expect(await table.getHeaderColumnPricing(3)).toContainText(headerCell.pricingText); + await expect(await table.getHeaderColumnOutlineButton(3)).toContainText(headerCell.outlineButtonText); + await expect(await table.getHeaderColumnBlueButton(3)).toContainText(headerCell.blueButtonText); + + // verify section row cell + const sectionCell = data.sectionRow2; + await expect(await table.getSectionRowTitle(2)).toContainText(sectionCell.sectionRowTitle); + await expect(await table.getSectionRowCell(2, 2)).toContainText(sectionCell.cell22); + }); + }); + + // Test 2 : Table (sticky) + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + const { data } = features[2]; + + await test.step('step-1: Go to Table block test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify table content/specs', async () => { + // verify sticky table header and attributes + await expect(await table.stickyTable).toBeVisible(); + await expect(await table.stickyRow).toHaveAttribute('class', 'row row-1 row-heading top-border-transparent'); + + // verify table row, column count + await expect(await table.rows).toHaveCount(data.rowsCount); + await expect(await table.headingRowColumns).toHaveCount(data.headerRowColCount); + await expect(await table.sectionRows).toHaveCount(data.sectionRowCount); + + // verify header row cell + const headerCell = data.headerCell4; + await expect(await table.getHeaderColumnTitle(4)).toContainText(headerCell.heading); + await expect(await table.getHeaderColumnPricing(4)).toContainText(headerCell.pricingText); + await expect(await table.getHeaderColumnOutlineButton(4)).toContainText(headerCell.outlineButtonText); + await expect(await table.getHeaderColumnBlueButton(4)).toContainText(headerCell.blueButtonText); + + // verify section row cell + const sectionCell = data.sectionRow2; + await expect(await table.getSectionRowTitle(2)).toContainText(sectionCell.sectionRowTitle); + await expect(await table.getSectionRowCell(2, 2)).toContainText(sectionCell.cell22); + }); + }); + + // Test 3 : Table (highlight, collapse, sticky) + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[3].path}${miloLibs}`); + const { data } = features[3]; + + await test.step('step-1: Go to Table block test page', async () => { + await page.goto(`${baseURL}${features[3].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[3].path}${miloLibs}`); + }); + + await test.step('step-2: Verify table content/specs', async () => { + // verify sticky table header and attributes + await expect(await table.collapseStickyTable).toBeVisible(); + await expect(table.highlightRow).toHaveClass(/row.*row-1.*row-highlight/); + await expect(table.stickyRow).toHaveClass(/row.*row-2.*row-heading/); + + // verify table row, column count + await expect(await table.rows).toHaveCount(data.rowsCount); + await expect(await table.headingRowColumns).toHaveCount(data.headerRowColCount); + await expect(await table.sectionRows).toHaveCount(data.sectionRowCount); + + // verify highlighter row + const highlighter = data.hightlightRow; + await expect(await table.getHighlightRowColumnTitle(1)).toContainText(highlighter.cell12); + await expect(await table.getHighlightRowColumnTitle(2)).toContainText(highlighter.cell13); + await expect(await table.getHighlightRowColumnTitle(3)).toContainText(highlighter.cell14); + + // verify header row cell + const headerCell = data.headerCell5; + await expect(await table.getHeaderColumnTitle(5)).toContainText(headerCell.heading); + await expect(await table.getHeaderColumnPricing(5)).toContainText(headerCell.pricingText); + await expect(await table.getHeaderColumnOutlineButton(5)).toContainText(headerCell.outlineButtonText); + await expect(await table.getHeaderColumnBlueButton(5)).toContainText(headerCell.blueButtonText); + + // verify section row cell + const sectionCell = data.sectionRow2; + await expect(await table.getSectionRowTitle(2)).toContainText(sectionCell.sectionRowTitle); + await expect(await table.getSectionRowCell(2, 2)).toContainText(sectionCell.cell22); + }); + }); + + // Test 4 : Table (merch) + test(`${features[4].name},${features[4].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[4].path}${miloLibs}`); + const { data } = features[4]; + + await test.step('step-1: Go to Table block test page', async () => { + await page.goto(`${baseURL}${features[4].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[4].path}${miloLibs}`); + }); + + await test.step('step-2: Verify table content/specs', async () => { + // verify merch table + await expect(await table.merchTable).toBeVisible(); + + // verify table row, column count + await expect(await table.rows).toHaveCount(data.rowsCount); + await expect(await table.headingRowColumns).toHaveCount(data.headerRowColCount); + await expect(await table.sectionRows).toHaveCount(data.sectionRowCount); + + // verify merch table header row cell + const headerCell = data.headerCell1; + await expect(await table.getHeaderColumnTitle(1)).toContainText(headerCell.heading); + await expect(await table.getHeaderColumnPricing(1)).toContainText(headerCell.pricingText); + await expect(await table.getHeaderColumnAdditionalText(1)).toContainText(headerCell.AdditionalText); + await expect(await table.getHeaderColumnOutlineButton(1)).toContainText(headerCell.outlineButtonText); + await expect(await table.getHeaderColumnBlueButton(1)).toContainText(headerCell.blueButtonText); + + // verify merch table section row cell + const sectionCell = data.sectionRow2; + await expect(await table.getSectionRowMerchContent(2)).toContainText(sectionCell.merchContent); + await expect(await table.getSectionRowMerchContentImg(2)).toBeVisible(); + }); + }); +}); diff --git a/nala/blocks/tabs/tabs.page.js b/nala/blocks/tabs/tabs.page.js new file mode 100644 index 0000000000..30aa9718a3 --- /dev/null +++ b/nala/blocks/tabs/tabs.page.js @@ -0,0 +1,26 @@ +export default class Tabs { + constructor(page, nth = 0) { + this.page = page; + // tabs locators + this.tab = this.page.locator('.tabs').nth(nth); + this.xlTab = this.page.locator('.tabs.xl-spacing').nth(nth); + this.queitDarkTab = this.page.locator('.tabs.quiet.dark.center').nth(nth); + // tabs list + this.tabList = this.tab.locator('.tabList'); + this.tabListContainer = this.tabList.locator('.tab-list-container'); + this.tabsCount = this.tabListContainer.locator('button[role="tab"]'); + this.tab1 = this.tabListContainer.locator('button[role="tab"]').nth(0); + this.tab2 = this.tabListContainer.locator('button[role="tab"]').nth(1); + this.tab3 = this.tabListContainer.locator('button[role="tab"]').nth(2); + this.tab9 = this.tabListContainer.locator('button[role="tab"]:nth-child(9)'); + // tabs panel and content + this.tabContent = this.tab.locator('.tab-content > .tab-content-container'); + this.tab1Panel = this.tabContent.locator('div[role="tabpanel"]:nth-child(1)'); + this.tab2Panel = this.tabContent.locator('div[role="tabpanel"]:nth-child(2)'); + this.tab3Panel = this.tabContent.locator('div[role="tabpanel"]:nth-child(3)'); + this.tab9Panel = this.tabContent.locator('div[role="tabpanel"]:nth-child(9)'); + + this.leftArrow = this.tab.locator('.tab-paddles > .paddle-left'); + this.rightArrow = this.tab.locator('.tab-paddles > .paddle-right'); + } +} diff --git a/nala/blocks/tabs/tabs.spec.js b/nala/blocks/tabs/tabs.spec.js new file mode 100644 index 0000000000..1edd7cfc0c --- /dev/null +++ b/nala/blocks/tabs/tabs.spec.js @@ -0,0 +1,37 @@ +module.exports = { + FeatureName: 'Tabs Block', + features: [ + { + tcid: '0', + name: '@Tabs (xl-spacing)', + path: '/drafts/nala/blocks/tabs/tabs-xl-spacing', + data: { + tabsCount: 3, + activeTab: 2, + tab1Text: 'Here is tab 1 content', + tab2Text: 'Here is tab 2 content and it is active tab', + tab3Text: 'Here is tab 3 content', + }, + tags: '@tabs @smoke @regression @milo @t1', + }, + { + tcid: '1', + name: '@Tabs (Quiet, Dark, Center)', + path: '/drafts/nala/blocks/tabs/tabs-quiet-dark-center', + data: { + tabsCount: 3, + activeTab: 2, + tab1Text: 'Here is tab 1 content', + tab2Text: 'Here is tab 2 content and it is active tab', + tab3Text: 'Here is tab 3 content', + }, + tags: '@tabs @smoke @t1 @regression @milo', + }, + { + tcid: '2', + name: 'Tabs scrolling', + path: '/drafts/nala/blocks/tabs/tabs-scrolling', + tags: '@tabs @tabs-scrolling @smoke @regression @milo @bacom', + }, + ], +}; diff --git a/nala/blocks/tabs/tabs.test.js b/nala/blocks/tabs/tabs.test.js new file mode 100644 index 0000000000..ec1176948d --- /dev/null +++ b/nala/blocks/tabs/tabs.test.js @@ -0,0 +1,141 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import { features } from './tabs.spec.js'; +import TabBlock from './tabs.page.js'; + +let tab; + +const miloLibs = process.env.MILO_LIBS || ''; +const INTERVALS = Array(5).fill(1000); + +test.describe('Milo Tab block feature test suite', () => { + test.beforeEach(async ({ page }) => { + tab = new TabBlock(page); + }); + + // Test 0 : Tabs (xl-spacing) + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + const { data } = features[0]; + + await test.step('step-1: Go to Tabs block feature test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify tabs content/specs', async () => { + await expect(await tab.xlTab).toBeVisible(); + await expect(await tab.tabsCount).toHaveCount(data.tabsCount); + // verify default tab contents + await expect(await tab.tab2).toHaveAttribute('aria-selected', 'true'); + await expect(await tab.tab2Panel).toBeVisible(); + await expect(await tab.tab2Panel).toContainText(data.tab2Text); + + // click tabs and verify contents + await expect(await tab.tab1).toHaveAttribute('aria-selected', 'false'); + await tab.tab1.click(); + await expect(await tab.tab1Panel).toBeVisible(); + await expect(await tab.tab1Panel).toContainText(data.tab1Text); + + await expect(await tab.tab3).toHaveAttribute('aria-selected', 'false'); + await tab.tab3.click(); + await expect(await tab.tab3Panel).toBeVisible(); + await expect(await tab.tab3Panel).toContainText(data.tab3Text); + }); + }); + + // Test 1 : Tabs (Quiet, Dark, Center) + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + const { data } = features[1]; + + await test.step('step-1: Go to Tabs block feature test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify tabs content/specs', async () => { + await expect(await tab.queitDarkTab).toBeVisible(); + await expect(await tab.tabsCount).toHaveCount(data.tabsCount); + // verify default tab contents + await expect(await tab.tab2).toHaveAttribute('aria-selected', 'true'); + await expect(await tab.tab2Panel).toBeVisible(); + await expect(await tab.tab2Panel).toContainText(data.tab2Text); + + // click tabs and verify contents + await expect(await tab.tab1).toHaveAttribute('aria-selected', 'false'); + await tab.tab1.click(); + await expect(await tab.tab1Panel).toBeVisible(); + await expect(await tab.tab1Panel).toContainText(data.tab1Text); + + await expect(await tab.tab3).toHaveAttribute('aria-selected', 'false'); + await tab.tab3.click(); + await expect(await tab.tab3Panel).toBeVisible(); + await expect(await tab.tab3Panel).toContainText(data.tab3Text); + }); + }); + + test(`Tabs scrolling with arrow buttons, ${features[2].tags}`, async ({ page, baseURL, isMobile }) => { + console.log(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('networkidle'); + + await test.step('checking the setup', async () => { + await expect(tab.tab1).toBeVisible(); + await expect(tab.tab1Panel).toBeVisible(); + await expect(tab.tab9).toBeVisible(); + await expect(tab.tab9Panel).not.toBeVisible(); + await expect(tab.tab1).toBeInViewport(); + await expect(tab.tab9).not.toBeInViewport(); + await expect(await tab.tab1.getAttribute('aria-selected')).toBe('true'); + await expect(await tab.tab9.getAttribute('aria-selected')).toBe('false'); + }); + + await test.step('select the right tab arrow to get to the last tab', async () => { + if (isMobile) { + await expect(async () => { + await tab.rightArrow.click(); + await expect(tab.tab9).toBeInViewport({ timeout: 1000 }); + await expect(tab.leftArrow).toBeVisible({ timeout: 1000 }); + }).toPass({ intervals: INTERVALS }); + } else { + await tab.rightArrow.click(); + await expect(tab.tab9).toBeInViewport(); + await expect(tab.leftArrow).toBeVisible(); + } + await tab.tab9.click(); + + await expect(await tab.tab1.getAttribute('aria-selected')).toBe('false'); + await expect(await tab.tab9.getAttribute('aria-selected')).toBe('true'); + await expect(tab.tab1).not.toBeInViewport(); + await expect(tab.tab9).toBeInViewport(); + await expect(tab.tab1Panel).not.toBeVisible(); + await expect(tab.tab9Panel).toBeVisible(); + }); + + await test.step('select the left tab arrow to get back to the first tab', async () => { + if (isMobile) { + await expect(async () => { + await tab.leftArrow.click(); + await expect(tab.tab1).toBeInViewport({ timeout: 1000 }); + await expect(tab.rightArrow).toBeVisible({ timeout: 1000 }); + }).toPass({ intervals: INTERVALS }); + } else { + await tab.leftArrow.click(); + await expect(tab.tab1).toBeInViewport(); + await expect(tab.rightArrow).toBeVisible(); + } + + await tab.tab1.click(); + + await expect(await tab.tab1.getAttribute('aria-selected')).toBe('true'); + await expect(await tab.tab9.getAttribute('aria-selected')).toBe('false'); + await expect(tab.tab1).toBeInViewport(); + await expect(tab.tab9).not.toBeInViewport(); + await expect(tab.tab1Panel).toBeVisible(); + await expect(tab.tab9Panel).not.toBeVisible(); + }); + }); +}); diff --git a/nala/blocks/text/text.page.js b/nala/blocks/text/text.page.js new file mode 100644 index 0000000000..ee70ba3b4f --- /dev/null +++ b/nala/blocks/text/text.page.js @@ -0,0 +1,115 @@ +export default class Text { + constructor(page, nth = 0) { + this.page = page; + // text locators + this.text = page.locator('.text').nth(nth); + this.textIntro = this.page.locator('.text.intro'); + this.textFullWidth = this.page.locator('.text.full-width'); + this.textFullWidthLarge = this.page.locator('.text.full-width.large'); + this.textLongFormLarge = this.page.locator('.text.long-form'); + this.textInsetLargeMSpacing = this.page.locator('.text.inset.medium.m-spacing'); + this.textlegal = this.page.locator('.text.legal.text-block.con-block.has-bg'); + this.textLinkFarm = this.page.locator('.text.link-farm.text-block.con-block.has-bg'); + + this.detailM = page.locator('.detail-m'); + this.introDetailM = page.locator('.detail-m'); + this.longFormDetailL = page.locator('.detail-l'); + this.legalDetail = page.locator('.foreground'); + + this.headline = this.text.locator('#text'); + this.introHeadline = this.text.locator('#text-intro'); + this.fullWidthHeadline = this.text.locator('#text-full-width'); + this.fullWidthLargeHeadline = this.text.locator('#text-full-width-large'); + this.longFormLargeHeadline = this.text.locator('#text-long-form-large'); + this.insetLargeMSpacingHeadline = this.text.locator('#text-inset-large-m-spacing'); + this.linkFarmHeadline = this.text.locator('#text-link-farm-title'); + this.linkFarmcolumnheading = this.text.locator('#heading-1'); + + this.linkFarmcolumns = this.text.locator('h3'); + this.linkColumnOne = this.text.locator('div div:nth-child(1) a'); + this.linkFormText = this.text.locator('p').nth(1); + + this.bodyXSS = this.text.locator('.body-xxs').first(); + this.bodyM = this.text.locator('.body-m').first(); + this.bodyL = this.text.locator('.body-l').first(); + this.propertiesHeadingM = this.text.locator('#properties-h3').first(); + + this.outlineButton = this.text.locator('.con-button.outline'); + this.actionAreaLink = this.page.locator('.body-m.action-area a').nth(1); + this.bodyLink = this.page.locator('.body-m a'); + + this.insetLargeMSpacingList1 = this.page.locator('.text.inset.medium.m-spacing ul').nth(0); + this.listOneItems = this.insetLargeMSpacingList1.locator('li'); + + this.insetLargeMSpacingList2 = this.page.locator('.text.inset.medium.m-spacing ul').nth(1); + this.listTwoItems = this.insetLargeMSpacingList2.locator('li'); + + this.generalTermsOfUse = this.textlegal.locator('.body-xxs').nth(1); + this.publishText = this.textlegal.locator('.body-xxs').nth(2); + this.generalTerms = this.textlegal.locator('.body-xxs').nth(4); + this.legalInfoLink = this.textlegal.locator('.body-xxs').nth(5); + + // text block contents css + this.cssProperties = { + 'detail-m': { + 'font-size': '12px', + 'line-height': '15px', + }, + 'detail-l': { + 'font-size': '16px', + 'line-height': '20px', + }, + 'heading-s': { + 'font-size': '20px', + 'line-height': '25px', + }, + 'heading-m': { + 'font-size': '24px', + 'line-height': '30px', + }, + 'heading-l': { + 'font-size': '28px', + 'line-height': '35px', + }, + 'heading-xl': { + 'font-size': '36px', + 'line-height': '45px', + }, + 'body-xss': { + 'font-size': '12px', + 'line-height': '18px', + }, + 'body-m': { + 'font-size': '18px', + 'line-height': '27px', + }, + 'body-l': { + 'font-size': '20px', + 'line-height': '30px', + }, + foreground: { + 'font-size': '12px', + 'line-height': '18px', + }, + }; + + // text block contents attributes + this.attProperties = { + text: { class: 'text text-block con-block' }, + 'text-intro': { + class: 'text intro text-block con-block has-bg max-width-8-desktop xxl-spacing-top xl-spacing-bottom', + style: 'background: rgb(255, 255, 255);', + }, + 'text-full-width': { class: 'text full-width text-block con-block max-width-8-desktop center xxl-spacing' }, + 'text-full-width-large': { class: 'text full-width large text-block con-block max-width-8-desktop center xxl-spacing' }, + 'text-long-form-large': { class: 'text long-form large text-block con-block max-width-8-desktop' }, + 'text-inset-medium-m-spacing': { class: 'text inset medium m-spacing text-block con-block max-width-8-desktop' }, + 'text-legal': { class: 'text legal text-block con-block has-bg' }, + 'text-Link-farm': { + class: 'text link-farm text-block con-block has-bg', + style: 'background: rgb(255, 255, 255);', + }, + headingprops: { id: 'heading-1' }, + }; + } +} diff --git a/nala/blocks/text/text.spec.js b/nala/blocks/text/text.spec.js new file mode 100644 index 0000000000..c89c731333 --- /dev/null +++ b/nala/blocks/text/text.spec.js @@ -0,0 +1,97 @@ +/* eslint-disable max-len */ + +module.exports = { + BlockName: 'Text Block', + features: [ + { + tcid: '0', + name: '@Text', + path: '/drafts/nala/blocks/text/text', + data: { + h3Text: 'Text', + bodyText: 'Kick things off with hundreds of premium and free presets you can access with your Lightroom subscription.', + outlineButtonText: 'Learn more', + linkText: 'Explore the premium collection', + }, + tags: '@text @smoke @regression @milo', + }, + { + tcid: '1', + name: '@Text (intro)', + path: '/drafts/nala/blocks/text/text-intro', + data: { + detailText: 'Detail', + h2Text: 'Text (intro)', + bodyText: 'Body L Regular (20/30) Lorem ipsum dolor sit amet,', + }, + tags: '@text @full-intro @smoke @regression @milo', + }, + { + tcid: '2', + name: '@Text (full-width)', + path: '/drafts/nala/blocks/text/text-full-width', + data: { + h3Text: 'Text (full width)', + bodyText: 'Featuring over 600,000 hand-picked stock photos and graphics, ', + linkText: 'Explore the premium collection', + }, + tags: '@text @full-width @smoke @regression @milo', + }, + { + tcid: '3', + name: '@Text (full-width, large)', + path: '/drafts/nala/blocks/text/text-full-width-large', + data: { + h2Text: 'Text (full width, large)', + bodyText: 'Whether your team is creating multichannel campaign assets,', + linkText: 'Learn more our solution', + }, + tags: '@text @full-width-large @smoke @regression @milo', + }, + { + tcid: '4', + name: '@Text (long-form, large)', + path: '/drafts/nala/blocks/text/text-long-form-large', + data: { + detailText: 'Detail', + h2Text: 'Text (long form, large)', + bodyText: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr,', + }, + tags: '@text @long-form-large @smoke @regression @milo', + }, + { + tcid: '5', + name: '@Text (inset, medium, m-spacing)', + path: '/drafts/nala/blocks/text/text-inset-medium-m-spacing', + data: { + h3Text: 'Text (inset, large, m spacing)', + bodyText: 'Lorem ipsum dolor sit amet.', + listCount1: 3, + }, + tags: '@text @inset-medium-m-spacing @smoke @regression @milo', + }, + { + tcid: '6', + name: '@Text (legal)', + path: '/drafts/nala/blocks/text/text-legal', + data: { + termsOfUseText: 'Adobe General Terms of Use', + publishText: 'Published August 1, 2022. Effective as of September 19, 2022.', + generalTermsText: 'These General Terms of Use (“General Terms”), along with any applicable Additional Terms', + linkText: 'Please read complete legal information', + }, + tags: '@text @legal @smoke @regression @milo', + }, + { + tcid: '7', + name: '@Text (link-farm)', + path: '/drafts/nala/blocks/text/text-link-farm', + data: { + headingColumns: 4, + linksCount: 6, + linkText: 'example of a link', + }, + tags: '@text @link-farm @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/text/text.test.js b/nala/blocks/text/text.test.js new file mode 100644 index 0000000000..c4fd402ab2 --- /dev/null +++ b/nala/blocks/text/text.test.js @@ -0,0 +1,244 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import WebUtil from '../../libs/webutil.js'; +import { features } from './text.spec.js'; +import TextBlock from './text.page.js'; + +let text; +let webUtil; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Text Block test suite', () => { + test.beforeEach(async ({ page }) => { + text = new TextBlock(page); + webUtil = new WebUtil(page); + }); + + // Test 0 : Text + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + const { data } = features[0]; + + await test.step('step-1: Go to Text block test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Text specs', async () => { + await expect(await text.text).toBeVisible(); + await expect(await text.headline).toContainText(data.h3Text); + await expect(await text.bodyM).toContainText(data.bodyText); + await expect(await text.outlineButton).toContainText(data.outlineButtonText); + + expect(await webUtil.verifyCSS(text.headline, text.cssProperties['heading-l'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.bodyM, text.cssProperties['body-m'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.actionAreaLink, text.cssProperties['body-m'])).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await text.text).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('text', 1)); + await expect(await text.outlineButton).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.outlineButtonText, 1, data.h3Text)); + await expect(await text.actionAreaLink).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.linkText, 2, data.h3Text)); + }); + }); + + // Test 1 : Text (intro) + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + const { data } = features[1]; + + await test.step('step-1: Go to Text (intro) block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Text (intro) specs', async () => { + await expect(text.textIntro).toBeVisible(); + await expect(await text.introHeadline).toContainText(data.h2Text); + await expect(await text.bodyM).toContainText(data.bodyText); + + expect(await webUtil.verifyAttributes(text.textIntro, text.attProperties['text-intro'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.introDetailM, text.cssProperties['detail-m'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.introHeadline, text.cssProperties['heading-l'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.bodyM, text.cssProperties['body-m'])).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await text.textIntro).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('text', 1)); + }); + }); + + // Test 2 : Text (full-width) + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + const { data } = features[2]; + + await test.step('step-1: Go to Text (full width) block test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Text (full width) specs', async () => { + await expect(text.textFullWidth).toBeVisible(); + + await expect(await text.fullWidthHeadline).toContainText(data.h3Text); + await expect(await text.bodyM).toContainText(data.bodyText); + await expect(await text.bodyLink).toContainText(data.linkText); + + expect(await webUtil.verifyAttributes(await text.textFullWidth, text.attProperties['text-full-width'])).toBeTruthy(); + expect(await webUtil.verifyCSS(await text.fullWidthHeadline, text.cssProperties['heading-l'])).toBeTruthy(); + expect(await webUtil.verifyCSS(await text.bodyM, text.cssProperties['body-m'])).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await text.textFullWidth).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('text', 1)); + await expect(await text.bodyLink).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.linkText, 1, data.h3Text)); + }); + }); + + // Test 3 : Text (full-width, large) + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[3].path}${miloLibs}`); + const { data } = features[3]; + + await test.step('step-1: Go to text (full-width, large) block test page', async () => { + await page.goto(`${baseURL}${features[3].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[3].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Text (full-width, large) specs', async () => { + await expect(text.textFullWidthLarge).toBeVisible(); + + await expect(await text.fullWidthLargeHeadline).toContainText(data.h2Text); + await expect(await text.bodyM).toContainText(data.bodyText); + await expect(await text.bodyLink).toContainText(data.linkText); + + expect(await webUtil.verifyAttributes(text.textFullWidthLarge, text.attProperties['text-full-width-large'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.fullWidthLargeHeadline, text.cssProperties['heading-xl'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.bodyM, text.cssProperties['body-m'])).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await text.textFullWidthLarge).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('text', 1)); + await expect(await text.bodyLink).toHaveAttribute('daa-ll', await webUtil.getLinkDaall(data.linkText, 1, data.h2Text)); + }); + }); + + // Test 4 : Text (long-form, large) + test(`${features[4].name},${features[4].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[4].path}${miloLibs}`); + const { data } = features[4]; + + await test.step('step-1: Go to Text (long form, large) block test page', async () => { + await page.goto(`${baseURL}${features[4].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[4].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Text (long form, large) specs', async () => { + await expect(await text.textLongFormLarge).toBeVisible(); + + await expect(await text.longFormDetailL).toContainText(data.detailText); + await expect(await text.longFormLargeHeadline).toContainText(data.h2Text); + await expect(await text.bodyL).toContainText(data.bodyText); + + expect(await webUtil.verifyAttributes(text.textLongFormLarge, text.attProperties['text-long-form-large'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.longFormDetailL, text.cssProperties['detail-l'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.longFormLargeHeadline, text.cssProperties['heading-l'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.bodyL, text.cssProperties['body-l'])).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await text.textLongFormLarge).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('text', 1)); + }); + }); + + // Test 5 : Text (inset, medium, m-spacing) + test(`${features[5].name},${features[5].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[5].path}${miloLibs}`); + const { data } = features[5]; + + await test.step('step-1: Go to Text (inset, medium, m-spacing ) block test page', async () => { + await page.goto(`${baseURL}${features[5].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[5].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Text (inset, large, m spacing) specs', async () => { + await expect(await text.textInsetLargeMSpacing).toBeVisible(); + + await expect(await text.insetLargeMSpacingHeadline).toContainText(data.h3Text); + await expect(await text.bodyL).toContainText(data.bodyText); + await expect(await text.listOneItems).toHaveCount(data.listCount1); + + expect(await webUtil.verifyAttributes(text.textInsetLargeMSpacing, text.attProperties['text-inset-medium-m-spacing'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.insetLargeMSpacingHeadline, text.cssProperties['heading-m'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.bodyL, text.cssProperties['body-l'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.propertiesHeadingM, text.cssProperties['heading-m'])).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await text.textInsetLargeMSpacing).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('text', 1)); + }); + }); + + // Test 6 : Text (legal) + test(`${features[6].name},${features[6].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[6].path}${miloLibs}`); + const { data } = features[6]; + + await test.step('step-1: Go to Text (legal) block test page', async () => { + await page.goto(`${baseURL}${features[6].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[6].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Text (legal) specs', async () => { + await expect(await text.textlegal).toBeVisible(); + + await expect(await text.generalTermsOfUse).toContainText(data.termsOfUseText); + await expect(await text.publishText).toContainText(data.publishText); + await expect(await text.generalTerms).toContainText(data.generalTermsText); + await expect(await text.legalInfoLink).toContainText(data.linkText); + + expect(await webUtil.verifyAttributes(text.textlegal, text.attProperties['text-legal'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.bodyXSS, text.cssProperties['body-xss'])).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await text.textlegal).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('text', 1)); + }); + }); + + test(`${features[7].name},${features[7].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[7].path}${miloLibs}`); + const { data } = features[7]; + + await test.step('step-1: Go to Text (link-farm) block test page', async () => { + await page.goto(`${baseURL}${features[7].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[7].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Text (link-farm) specs', async () => { + await expect(await text.textLinkFarm).toBeVisible(); + + await expect(await text.linkFarmcolumns).toHaveCount(data.headingColumns); + await expect(await text.linkColumnOne).toHaveCount(data.linksCount); + await expect(await text.linkFormText).toContainText(data.linkText); + + expect(await webUtil.verifyAttributes(text.textLinkFarm, text.attProperties['text-Link-farm'])).toBeTruthy(); + expect(await webUtil.verifyCSS(text.linkFarmHeadline, text.cssProperties['heading-l'])).toBeTruthy(); + expect(await webUtil.verifyAttributes(text.linkFarmcolumnheading, text.attProperties.headingprops)).toBeTruthy(); + }); + + await test.step('step-3: Verify analytics attributes', async () => { + await expect(await text.textLinkFarm).toHaveAttribute('daa-lh', await webUtil.getBlockDaalh('text', 1)); + }); + }); +}); diff --git a/nala/blocks/video/video.page.js b/nala/blocks/video/video.page.js new file mode 100644 index 0000000000..a68ae99911 --- /dev/null +++ b/nala/blocks/video/video.page.js @@ -0,0 +1,72 @@ +export default class Video { + constructor(page, nth = 0) { + this.page = page; + + // video locators + this.section = this.page.locator('.section').nth(nth); + this.content = this.page.locator('.content').nth(nth); + this.fragment = this.page.locator('.fragment'); + this.video = this.page.locator('.content video'); + this.videoSource = this.video.locator('source'); + this.miloVideo = this.page.locator('.milo-video'); + this.iframe = this.page.locator('iframe').first(); + this.mpcPlayerTitle = this.page.frameLocator('iframe').first().locator('.mpc-player__title'); + this.mpcPlayButton = this.page.frameLocator('iframe').first().locator('button .mpc-large-play.mpc-player__large-play'); + this.mpcMutedButton = this.page.frameLocator('iframe').first().locator('.mpc-player button[aria-label="Mute"]'); + this.mpcMutedLabel = this.page.frameLocator('iframe').first().locator('.mpc-player button[aria-label="Mute"] span'); + this.youtubePlayButton = this.page.locator('button.lty-playbtn'); + this.liteYoutube = this.page.locator('lite-youtube'); + this.modalVideo = this.fragment.locator('video'); + this.modalVideoSource = this.modalVideo.locator('source'); + this.consonantCardsGrid = this.page.locator('.consonant-CardsGrid'); + this.consonantCards = this.consonantCardsGrid.locator('.card.consonant-Card'); + this.video = this.page.locator('.content video'); + this.videoSource = this.video.locator('source'); + + // video block attributes + this.attributes = { + 'video.default': { + playsinline: '', + controls: '', + }, + 'video.source': { + type: 'video/mp4', + src: /.*.mp4/, + }, + 'video.autoplay': { + playsinline: '', + autoplay: '', + loop: '', + muted: '', + }, + 'video.autoplay.once': { + playsinline: '', + autoplay: '', + muted: '', + }, + 'video.hover.play': { + playsinline: '', + autoplay: '', + muted: '', + 'data-hoverplay': '', + 'data-mouseevent': 'true', + }, + 'iframe-mpc': { + class: 'adobetv', + scrolling: 'no', + allowfullscreen: '', + loading: 'lazy', + }, + 'iframe-youtube': { + class: 'youtube', + scrolling: 'no', + allowfullscreen: '', + allow: 'encrypted-media; accelerometer; gyroscope; picture-in-picture', + }, + analytics: { + 'section.daa-lh': { 'daa-lh': /s[1-9]/ }, + 'content.daa-lh': { 'daa-lh': /b[1-9]|content|default|default/ }, + }, + }; + } +} diff --git a/nala/blocks/video/video.spec.js b/nala/blocks/video/video.spec.js new file mode 100644 index 0000000000..80ca3350d8 --- /dev/null +++ b/nala/blocks/video/video.spec.js @@ -0,0 +1,84 @@ +module.exports = { + FeatureName: 'Video Block', + features: [ + { + tcid: '0', + name: '@Video Default', + path: '/drafts/nala/blocks/video/default-video', + data: { h2Text: 'Default video' }, + tags: '@video @smoke @regression @milo', + }, + { + tcid: '1', + name: '@Video autoplay loop', + path: '/drafts/nala/blocks/video/video-autoplay-loop', + data: { h2Text: 'Autoplay enabled video' }, + tags: '@video @smoke @regression @milo', + }, + { + tcid: '2', + name: '@Video autoplay loop once', + path: '/drafts/nala/blocks/video/autoplay-loop-once', + data: { h2Text: 'Autoplay once enabled video' }, + tags: '@video @smoke @regression @milo', + }, + { + tcid: '3', + name: '@Video hover play', + path: '/drafts/nala/blocks/video/video-hover-play', + data: { h2Text: 'Hover play enabled video (combined with #_autoplay1 for feature to work)' }, + tags: '@video @smoke @regression @milo', + }, + { + tcid: '4', + name: '@MPC Video', + path: '/drafts/nala/blocks/video/mpc-video', + data: { + h1Title: '1856730_Summit_2021_Marquee_1440x1028_v1.0.mp4', + iframeTitle: 'Adobe Video Publishing Cloud Player', + source: 'https://video.tv.adobe.com/v/332632', + }, + tags: '@video @smoke @regression @milo', + }, + { + tcid: '5', + name: '@MPC Video Autoplay Looping', + path: '/drafts/nala/blocks/video/mpc-video-autoplay-looping', + data: { + iframeTitle: 'Adobe Video Publishing Cloud Player', + source: 'https://video.tv.adobe.com/v/332632?autoplay=true&end=replay', + }, + tags: '@video @smoke @regression @milo', + }, + { + tcid: '6', + name: '@Youtube Video ', + path: '/drafts/nala/blocks/video/youtube-video', + data: { + h1Text: 'YouTube video', + playLabel: 'Adobe MAX Keynote 2022 | Adobe Creative Cloud', + source: 'https://www.youtube.com/embed/OfQKEzgPaBA?', + videoId: 'OfQKEzgPaBA', + }, + tags: '@video @smoke @regression @milo', + }, + { + tcid: '7', + name: '@Fragment Modal video inline', + path: '/drafts/nala/blocks/video/fragments-modal-video-autoplay', + data: + { source: 'https://main--milo--adobecom.hlx.live/libs/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb.mp4' }, + tags: '@video @smoke @regression @milo', + }, + { + tcid: '8', + name: '@Modal video with cards', + path: '/drafts/nala/blocks/video/modal-video-with-cards', + data: { + cardsCount: 3, + source: 'https://milo.adobe.com/libs/media_1e798d01c6ddc7e7eadc8f134d69e4f8d7193fdbb.mp4', + }, + tags: '@video @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/video/video.test.js b/nala/blocks/video/video.test.js new file mode 100644 index 0000000000..ad998de0a6 --- /dev/null +++ b/nala/blocks/video/video.test.js @@ -0,0 +1,213 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import WebUtil from '../../libs/webutil.js'; +import { features } from './video.spec.js'; +import VideoBlock from './video.page.js'; + +let webUtil; +let video; +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Video Block test suite', () => { + test.beforeEach(async ({ page }) => { + webUtil = new WebUtil(page); + video = new VideoBlock(page); + }); + + // Test 0 : Video default + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + const { data } = features[0]; + + await test.step('step-1: Go to video block test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify video block content/specs', async () => { + await expect(await video.video).toBeVisible(); + await expect(await video.content).toContainText(data.h2Text); + + await expect(await webUtil.verifyAttributes(video.video, video.attributes['video.default'])).toBeTruthy(); + await expect(await webUtil.verifyAttributes(video.videoSource, video.attributes['video.source'])).toBeTruthy(); + }); + }); + + // Test 1 : Video autoplay loop + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + const { data } = features[1]; + + await test.step('step-1: Go to video block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify video block content/specs', async () => { + await expect(await video.video).toBeVisible(); + await expect(await video.content).toContainText(data.h2Text); + + expect(await webUtil.verifyAttributes(video.video, video.attributes['video.autoplay'])).toBeTruthy(); + expect(await webUtil.verifyAttributes(video.videoSource, video.attributes['video.source'])).toBeTruthy(); + }); + }); + + // Test 2 : Video autoplay loop once + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + const { data } = features[2]; + + await test.step('step-1: Go to video block test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify video block content/specs', async () => { + await expect(await video.video).toBeVisible(); + await expect(await video.content).toContainText(data.h2Text); + + expect(await webUtil.verifyAttributes(video.video, video.attributes['video.autoplay.once'])).toBeTruthy(); + expect(await webUtil.verifyAttributes(video.videoSource, video.attributes['video.source'])).toBeTruthy(); + }); + }); + + // Test 3 : Video hover play + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[3].path}${miloLibs}`); + const { data } = features[3]; + + await test.step('step-1: Go to video block test page', async () => { + await page.goto(`${baseURL}${features[3].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[3].path}${miloLibs}`); + }); + + await test.step('step-2: Verify video block content/specs', async () => { + await expect(await video.video).toBeVisible(); + await expect(await video.content).toContainText(data.h2Text); + await new Promise((resolve) => { setTimeout(resolve, 5000); }); + await video.video.hover({ force: true }); + + expect(await webUtil.verifyAttributes(video.video, video.attributes['video.autoplay.once'])).toBeTruthy(); + expect(await webUtil.verifyAttributes(video.videoSource, video.attributes['video.source'])).toBeTruthy(); + }); + }); + + // Test 4 : MPC Video + test(`${features[4].name},${features[4].tags}`, async ({ page, baseURL }) => { + test.slow(); + console.info(`[Test Page]: ${baseURL}${features[4].path}${miloLibs}`); + const { data } = features[4]; + + await test.step('step-1: Go to video block test page', async () => { + await page.goto(`${baseURL}${features[4].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[4].path}${miloLibs}`); + }); + + await test.step('step-2: Verify video block content/specs', async () => { + await expect(await video.miloVideo).toBeVisible(); + await expect(await video.iframe).toBeVisible(); + + await expect(await video.iframe).toHaveAttribute('title', data.iframeTitle); + await expect(await video.iframe).toHaveAttribute('src', data.source); + expect(await webUtil.verifyAttributes(video.iframe, video.attributes['iframe-mpc'])).toBeTruthy(); + }); + }); + + // Test 5 : MPC Video Autoplay Looping + test(`${features[5].name},${features[5].tags}`, async ({ page, baseURL }) => { + test.slow(); + console.info(`[Test Page]: ${baseURL}${features[5].path}${miloLibs}`); + const { data } = features[5]; + + await test.step('step-1: Go to video block test page', async () => { + await page.goto(`${baseURL}${features[5].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[5].path}${miloLibs}`); + }); + + await test.step('step-2: Verify video block content/specs', async () => { + await expect(await video.miloVideo).toBeVisible(); + await expect(await video.iframe).toHaveAttribute('title', data.iframeTitle); + await expect(await video.iframe).toHaveAttribute('src', data.source); + expect(await webUtil.verifyAttributes(video.iframe, video.attributes['iframe-mpc'])).toBeTruthy(); + }); + }); + + // Test 6 : Youtube Video + test(`${features[6].name},${features[6].tags}`, async ({ page, baseURL }) => { + test.slow(); + console.info(`[Test Page]: ${baseURL}${features[6].path}${miloLibs}`); + const { data } = features[6]; + + await test.step('step-1: Go to video block test page', async () => { + await page.goto(`${baseURL}${features[6].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[6].path}${miloLibs}`); + }); + + await test.step('step-2: Verify video block content/specs', async () => { + await expect(await video.miloVideo).toBeVisible(); + await expect(await video.youtubePlayButton).toBeVisible(); + await expect(await video.youtubePlayButton).toHaveAttribute('type', 'button'); + + await expect(await video.liteYoutube).toHaveAttribute('playlabel', data.playLabel); + await expect(await video.liteYoutube).toHaveAttribute('videoid', data.videoId); + }); + }); + + // Test 7 : Modal Video default + test(`${features[7].name},${features[7].tags}`, async ({ page, baseURL }) => { + test.slow(); + console.info(`[Test Page]: ${baseURL}${features[7].path}${miloLibs}`); + // const { data } = features[7]; + + await test.step('step-1: Go to video block test page', async () => { + await page.goto(`${baseURL}${features[7].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[7].path}${miloLibs}`); + }); + + await test.step('step-2: Verify video block content/specs', async () => { + await expect(await video.modalVideo).toBeVisible(); + + expect(await webUtil.verifyAttributes(video.modalVideo, video.attributes['video.autoplay'])).toBeTruthy(); + expect(await webUtil.verifyAttributes(video.modalVideoSource, video.attributes['video.source'])).toBeTruthy(); + + const srcAttributeValue = await video.modalVideoSource.getAttribute('src'); + console.log('[video source]:', srcAttributeValue); + expect(srcAttributeValue).not.toBe(''); + }); + }); + + // Test 8 : Modal video with cards + test(`${features[8].name},${features[8].tags}`, async ({ page, baseURL }) => { + test.slow(); + console.info(`[Test Page]: ${baseURL}${features[8].path}${miloLibs}`); + const { data } = features[8]; + + await test.step('step-1: Go to video block test page', async () => { + await page.goto(`${baseURL}${features[8].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[8].path}${miloLibs}`); + }); + + await test.step('step-2: Verify consonant cards with modal video block content/specs', async () => { + await expect(await video.consonantCardsGrid).toBeVisible(); + await expect(await video.consonantCards.nth(0)).toBeVisible(); + await expect(await video.consonantCards).toHaveCount(data.cardsCount); + + await expect(await video.modalVideo).toBeVisible(); + expect(await webUtil.verifyAttributes(video.modalVideo, video.attributes['video.autoplay'])).toBeTruthy(); + expect(await webUtil.verifyAttributes(video.modalVideoSource, video.attributes['video.source'])).toBeTruthy(); + + const srcAttributeValue = await video.modalVideoSource.getAttribute('src'); + console.log('[video source]:', srcAttributeValue); + expect(srcAttributeValue).not.toBe(''); + }); + }); +}); diff --git a/nala/blocks/zpattern/zpattern.page.js b/nala/blocks/zpattern/zpattern.page.js new file mode 100644 index 0000000000..7ca8ddc2ae --- /dev/null +++ b/nala/blocks/zpattern/zpattern.page.js @@ -0,0 +1,39 @@ +export default class ZPattern { + constructor(page, nth = 0) { + this.page = page; + // z-pattern locators + this.zPattern = page.locator('.z-pattern').nth(nth); + + // zpatter header + this.zPatternHeader = this.zPattern.locator('.heading-row'); + this.zPatternPText = this.zPatternHeader.locator('p'); + + this.smallIntroHeadingText = this.zPattern.locator('#small-default-intro-text-optional'); + this.mediumIntroHeadingText = this.zPattern.locator('#medium-intro-text-optional'); + this.largeIntroHeadingText = this.zPattern.locator('#large-intro-text-optional'); + this.darkIntroHeadingText = this.zPattern.locator('#intuitive-block-authoring'); + + this.zPatternMediaBlocks = this.zPattern.locator('.media'); + this.mediaBlocks = this.zPattern.locator('.media'); + + // zpattern contents attributes + this.attProperties = { + 'z-pattern': { style: 'background: rgb(245, 245, 245);' }, + 'z-pattern-dark': { style: 'background: rgb(50, 50, 50);' }, + 'small-default-intro-text-optional': { class: 'heading-l headline' }, + 'medium-intro-text-optional': { class: 'heading-l headline' }, + 'large-intro-text-optional': { class: 'heading-xl headline' }, + 'dark-intro-text-optional': { class: 'heading-l headline' }, + 'media-medium': { class: 'media medium con-block' }, + 'small-media-reversed': { class: 'media small media-reversed con-block' }, + 'medium-media-reversed': { class: 'media medium media-reversed con-block' }, + 'medium-media-reverse-mobile': { class: 'media medium con-block media-reverse-mobile' }, + 'large-media-reversed': { class: 'media large media-reversed con-block' }, + 'media-image': { + width: '600', + height: '300', + }, + + }; + } +} diff --git a/nala/blocks/zpattern/zpattern.spec.js b/nala/blocks/zpattern/zpattern.spec.js new file mode 100644 index 0000000000..a531eb438a --- /dev/null +++ b/nala/blocks/zpattern/zpattern.spec.js @@ -0,0 +1,131 @@ +module.exports = { + BlockName: 'ZPattern', + features: [ + { + tcid: '0', + name: '@ZPattern', + path: '/drafts/nala/blocks/zpattern/z-pattern', + data: { + headingText: 'Medium Intro Text (optional)', + introText: 'Perspiciatis unde omnis iste natus error', + mediaBlockCount: 3, + mediaBlocks: [ + { + detailText: 'Detail M 12/15', + h2Text: 'Heading M 24/30 z-pattern medium', + bodyText: 'Body S 16/24 Lorem ipsum dolor sit amet', + blueButtonText: 'learn more', + }, + { + detailText: 'Detail M 12/15', + h2Text: 'Heading M 24/30 z-pattern medium', + bodyText: 'Body S 16/24 Lorem ipsum dolor sit amet', + blueButtonText: 'learn more', + }, + { + detailText: 'Detail M 12/15', + h2Text: 'Heading M 24/30 z-pattern medium', + bodyText: 'Body S 16/24 Lorem ipsum dolor sit amet', + blueButtonText: 'learn more', + }, + ], + }, + tags: '@zpattern @smoke @regression @milo', + }, + { + tcid: '1', + name: '@ZPattern (small)', + path: '/drafts/nala/blocks/zpattern/z-pattern-small', + data: { + headingText: 'Small (default) Intro Text (optional)', + introText: 'Media blocks may use one of three background colors', + mediaBlockCount: 3, + mediaBlocks: [ + { + detailText: 'Detail M 12/15', + h2Text: 'Heading XS 18/22 z-pattern small', + bodyText: 'Body S 16/24 Lorem ipsum dolor sit amet', + blueButtonText: 'learn more', + }, + { + detailText: 'Detail M 12/15', + h2Text: 'Heading XS 18/22 z-pattern small', + bodyText: 'Body S 16/24 Lorem ipsum dolor sit amet', + blueButtonText: 'learn more', + }, + { + detailText: 'Detail M 12/15', + h2Text: 'Heading XS 18/22 z-pattern small', + bodyText: 'Body S 16/24 Lorem ipsum dolor sit amet', + blueButtonText: 'learn more', + }, + ], + }, + tags: '@zpattern @zpattern-small @smoke @regression @milo', + }, + + { + tcid: '2', + name: '@Zpattern (large)', + path: '/drafts/nala/blocks/zpattern/z-pattern-large', + data: { + headingText: 'Large Intro Text (optional)', + introText: 'Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium', + mediaBlockCount: 3, + mediaBlocks: [ + { + detailText: 'Detail L 16/20', + h2Text: 'Heading XL 36/45 z-pattern large', + bodyText: 'Body M 18/27 Lorem ipsum dolor sit amet', + blueButtonText: 'learn more', + }, + { + detailText: 'Detail L 16/20', + h2Text: 'Heading XL 36/45 z-pattern large', + bodyText: 'Body M 18/27 Lorem ipsum dolor sit amet', + blueButtonText: 'learn more', + }, + { + detailText: 'Detail L 16/20', + h2Text: 'Heading XL 36/45 z-pattern large', + bodyText: 'Body M 18/27 Lorem ipsum dolor sit amet', + blueButtonText: 'learn more', + }, + ], + }, + tags: '@zpattern @zpattern-large @smoke @regression @milo', + }, + + { + tcid: '3', + name: '@Zpattern (dark)', + path: '/drafts/nala/blocks/zpattern/z-pattern-dark', + data: { + headingText: 'Intuitive block authoring', + introText: 'Supports alternating or inline authoring preferences', + mediaBlockCount: 3, + mediaBlocks: [ + { + detailText: 'Detail M 12/15', + h2Text: 'Heading M 24/30 z-pattern medium', + bodyText: 'Body S 16/24 Lorem ipsum dolor sit amet', + blueButtonText: 'Learn More', + }, + { + detailText: 'Detail M 12/15', + h2Text: 'Heading M 24/30 z-pattern medium', + bodyText: 'Body S 16/24 Lorem ipsum dolor sit amet', + blueButtonText: 'Learn More', + }, + { + detailText: 'Detail M 12/15', + h2Text: 'Heading M 24/30 z-pattern medium', + bodyText: 'Body S 16/24 Lorem ipsum dolor sit amet', + blueButtonText: 'Learn More', + }, + ], + }, + tags: '@zpattern @zpattern-dark @smoke @regression @milo', + }, + ], +}; diff --git a/nala/blocks/zpattern/zpattern.test.js b/nala/blocks/zpattern/zpattern.test.js new file mode 100644 index 0000000000..2d85198164 --- /dev/null +++ b/nala/blocks/zpattern/zpattern.test.js @@ -0,0 +1,169 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console, no-plusplus */ +import { expect, test } from '@playwright/test'; +import WebUtil from '../../libs/webutil.js'; +import { features } from './zpattern.spec.js'; +import ZPatternBlock from './zpattern.page.js'; + +let webUtil; +let zpattern; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Milo Z Pattern Block test suite', () => { + test.beforeEach(async ({ page }) => { + webUtil = new WebUtil(page); + zpattern = new ZPatternBlock(page); + }); + + // Test 0 : ZPattern default block + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}${miloLibs}`); + const { data } = features[0]; + + await test.step('step-1: Go to Z Pattern block test page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('step-2: Verify Z Pattern block specs', async () => { + await expect(await zpattern.zPatternHeader).toContainText(data.headingText); + await expect(await zpattern.zPatternPText).toContainText(data.introText); + await expect(await zpattern.mediaBlocks).toHaveCount(data.mediaBlockCount); + + const mediaBlocks = await zpattern.mediaBlocks.all(); + const mediaBlocksArray = await Promise.all(mediaBlocks.map(async (block) => block)); + + for (let i = 0; i < mediaBlocksArray.length; i++) { + const mediaBlock = mediaBlocksArray[i]; + await expect(await mediaBlock.locator('.detail-m')).toContainText(data.mediaBlocks[i].detailText); + await expect(await mediaBlock.locator('.heading-m')).toContainText(data.mediaBlocks[i].h2Text); + await expect(await mediaBlock.locator('.body-s').nth(0)).toContainText(data.mediaBlocks[i].bodyText); + await expect(await mediaBlock.locator('.blue')).toContainText(data.mediaBlocks[i].blueButtonText); + + const image = await mediaBlock.locator('.image img').nth(0); + const classAttribute = await mediaBlock.getAttribute('class'); + const hasReversedClass = classAttribute.includes('media-reversed'); + + if (hasReversedClass) { + expect(await webUtil.verifyAttributes(mediaBlock, zpattern.attProperties['medium-media-reversed'])).toBeTruthy(); + } + expect(await webUtil.verifyAttributes(image, zpattern.attProperties['media-image'])).toBeTruthy(); + } + }); + }); + + // Test 1 :ZPattern (small) block + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}${miloLibs}`); + const { data } = features[1]; + + await test.step('step-1: Go to z-pattern (small) block test page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('step-2: Verify z-pattern (small) block specs', async () => { + await expect(await zpattern.zPatternHeader).toContainText(data.headingText); + await expect(await zpattern.zPatternPText).toContainText(data.introText); + await expect(await zpattern.mediaBlocks).toHaveCount(data.mediaBlockCount); + + const mediaBlocks = await zpattern.mediaBlocks.all(); + const mediaBlocksArray = await Promise.all(mediaBlocks.map(async (block) => block)); + + for (let i = 0; i < mediaBlocksArray.length; i++) { + const mediaBlock = mediaBlocksArray[i]; + await expect(await mediaBlock.locator('.detail-m')).toContainText(data.mediaBlocks[i].detailText); + await expect(await mediaBlock.locator('.heading-xs')).toContainText(data.mediaBlocks[i].h2Text); + await expect(await mediaBlock.locator('.body-s').nth(0)).toContainText(data.mediaBlocks[i].bodyText); + await expect(await mediaBlock.locator('.blue')).toContainText(data.mediaBlocks[i].blueButtonText); + + const image = await mediaBlock.locator('.image img').nth(0); + const classAttribute = await mediaBlock.getAttribute('class'); + const hasReversedClass = classAttribute.includes('media-reversed'); + + if (hasReversedClass) { + expect(await webUtil.verifyAttributes(mediaBlock, zpattern.attProperties['small-media-reversed'])).toBeTruthy(); + } + expect(await webUtil.verifyAttributes(image, zpattern.attProperties['media-image'])).toBeTruthy(); + } + }); + }); + + // Test 2 :Zpattern (large) block + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}${miloLibs}`); + const { data } = features[2]; + + await test.step('step-1: Go to z-pattern (large) block test page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('step-2: Verify z-pattern (large) block specs', async () => { + await expect(await zpattern.zPatternHeader).toContainText(data.headingText); + await expect(await zpattern.zPatternPText).toContainText(data.introText); + await expect(await zpattern.mediaBlocks).toHaveCount(data.mediaBlockCount); + + const mediaBlocks = await zpattern.mediaBlocks.all(); + const mediaBlocksArray = await Promise.all(mediaBlocks.map(async (block) => block)); + + for (let i = 0; i < mediaBlocksArray.length; i++) { + const mediaBlock = mediaBlocksArray[i]; + await expect(await mediaBlock.locator('.detail-l')).toContainText(data.mediaBlocks[i].detailText); + await expect(await mediaBlock.locator('.heading-xl')).toContainText(data.mediaBlocks[i].h2Text); + await expect(await mediaBlock.locator('.body-m').nth(0)).toContainText(data.mediaBlocks[i].bodyText); + await expect(await mediaBlock.locator('.blue')).toContainText(data.mediaBlocks[i].blueButtonText); + + const image = await mediaBlock.locator('.image img').nth(0); + const classAttribute = await mediaBlock.getAttribute('class'); + const hasReversedClass = classAttribute.includes('media-reversed'); + + if (hasReversedClass) { + expect(await webUtil.verifyAttributes(mediaBlock, zpattern.attProperties['large-media-reversed'])).toBeTruthy(); + } + expect(await webUtil.verifyAttributes(image, zpattern.attProperties['media-image'])).toBeTruthy(); + } + }); + }); + + // Test 3 :Zpattern (dark) block + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[3].path}${miloLibs}`); + const { data } = features[3]; + + await test.step('step-1: Go to z-pattern (large) block test page', async () => { + await page.goto(`${baseURL}${features[3].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[3].path}${miloLibs}`); + }); + + await test.step('step-2: Verify z-pattern (dark) block specs', async () => { + await expect(await zpattern.zPatternHeader).toContainText(data.headingText); + await expect(await zpattern.zPatternPText).toContainText(data.introText); + await expect(await zpattern.mediaBlocks).toHaveCount(data.mediaBlockCount); + + const mediaBlocks = await zpattern.mediaBlocks.all(); + const mediaBlocksArray = await Promise.all(mediaBlocks.map(async (block) => block)); + + for (let i = 0; i < mediaBlocksArray.length; i++) { + const mediaBlock = mediaBlocksArray[i]; + await expect(await mediaBlock.locator('.detail-m')).toContainText(data.mediaBlocks[i].detailText); + await expect(await mediaBlock.locator('.heading-m')).toContainText(data.mediaBlocks[i].h2Text); + await expect(await mediaBlock.locator('.body-s').nth(0)).toContainText(data.mediaBlocks[i].bodyText); + await expect(await mediaBlock.locator('.blue')).toContainText(data.mediaBlocks[i].blueButtonText); + + const image = await mediaBlock.locator('.image img').nth(0); + const classAttribute = await mediaBlock.getAttribute('class'); + const hasReversedClass = classAttribute.includes('media-reversed'); + + if (hasReversedClass) { + expect(await webUtil.verifyAttributes(mediaBlock, zpattern.attProperties['dark-media-reversed'])).toBeTruthy(); + } + expect(await webUtil.verifyAttributes(image, zpattern.attProperties['media-image'])).toBeTruthy(); + } + }); + }); +}); diff --git a/nala/features/commerce/commerce.page.js b/nala/features/commerce/commerce.page.js new file mode 100644 index 0000000000..60edc95c9e --- /dev/null +++ b/nala/features/commerce/commerce.page.js @@ -0,0 +1,19 @@ +export default class CommercePage { + constructor(page) { + this.page = page; + + this.price = page.locator('//span[@data-template="price"]'); + this.priceOptical = page.locator('//span[@data-template="optical"]'); + this.priceStrikethrough = page.locator('//span[@data-template="strikethrough"]'); + this.buyNowCta = page.locator('//a[contains(@daa-ll, "Buy now")]'); + this.freeTrialCta = page.locator('//a[contains(@daa-ll, "Free trial")]'); + this.merchCard = page.locator('merch-card'); + // universal nav login account type + this.loginType = page.locator('div.feds-profile > div > div > ul > li:nth-child(5) > button'); + // entitlement block locators + this.ccAllAppsCTA = page.locator('//*[contains(@daa-ll,"CC All Apps")]'); + this.photoshopBuyCTA = page.locator('//*[contains(@daa-ll,"Buy now-1--Photoshop")]'); + this.photoshopFreeCTA = page.locator('//*[contains(@daa-ll,"Free trial-2--Photoshop")]'); + this.switchModalIframe = page.locator('#switch-modal > div > iframe'); + } +} diff --git a/nala/features/commerce/commerce.spec.js b/nala/features/commerce/commerce.spec.js new file mode 100644 index 0000000000..51b3a27dd9 --- /dev/null +++ b/nala/features/commerce/commerce.spec.js @@ -0,0 +1,89 @@ +module.exports = { + name: 'Commerce', + features: [ + { + tcid: '0', + name: '@Commerce-Price-Term', + path: '/drafts/nala/features/commerce/prices-with-term', + tags: '@commerce @smoke @regression', + }, + { + tcid: '1', + name: '@Commerce-Price-Unit-Term', + path: '/drafts/nala/features/commerce/prices-with-term-unit', + tags: '@commerce @smoke @regression', + + }, + { + tcid: '2', + name: '@Commerce-Price-Taxlabel-Unit-Term', + path: '/drafts/nala/features/commerce/prices-with-term-unit-taxlabel', + tags: '@commerce @smoke @regression', + }, + { + tcid: '3', + name: '@Commerce-Promo', + path: '/drafts/nala/features/commerce/promo-placeholders', + data: { + promo: 'UMRM2MUSPr501YOC', + workflow: 'recommendation', + }, + tags: '@commerce @smoke @regression', + }, + { + tcid: '4', + name: '@Commerce-Upgrade-Entitlement', + path: '/drafts/nala/features/commerce/checkout-links', + data: { UpgradeCTATitle: 'Upgrade now' }, + tags: '@commerce @entitlement @smoke @regression @nopr', + }, + { + tcid: '5', + name: '@Commerce-Download-Entitlement', + path: '/drafts/nala/features/commerce/checkout-links', + data: { + DownloadCTATitle: 'Download', + TrialCTATitle: 'Free trial', + DownloadUrl: 'download/photoshop', + }, + tags: '@commerce @entitlement @smoke @regression @nopr', + }, + { + tcid: '6', + name: '@Commerce-KitchenSink-Smoke', + path: '/docs/library/kitchen-sink/merch-card', + tags: '@commerce @kitchensink @smoke @regression', + }, + { + tcid: '7', + name: '@Commerce-DE', + path: '/de/drafts/nala/features/commerce/promo-placeholders', + data: { + promo: 'PEMAP50AASTE2', + CO: 'co=DE', + lang: 'lang=de', + workflow: 'recommendation', + }, + tags: '@commerce @smoke @regression', + }, + { + tcid: '8', + name: '@Commerce-Old-Promo', + path: '/drafts/nala/features/commerce/promo-old-price', + data: { promo: 'UMRM2MUSPr501YOC' }, + tags: '@commerce @smoke @regression', + }, + { + tcid: '9', + name: '@Commerce-GB', + path: '/uk/drafts/nala/features/commerce/promo-placeholders', + data: { + promo: 'PEMAP50AASTE2', + CO: 'co=GB', + lang: 'lang=en', + workflow: 'recommendation', + }, + tags: '@commerce @smoke @regression', + }, + ], +}; diff --git a/nala/features/commerce/commerce.test.js b/nala/features/commerce/commerce.test.js new file mode 100644 index 0000000000..3f06a37479 --- /dev/null +++ b/nala/features/commerce/commerce.test.js @@ -0,0 +1,475 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console */ +import { expect, test } from '@playwright/test'; +import WebUtil from '../../libs/webutil.js'; +import { features } from './commerce.spec.js'; +import CommercePage from './commerce.page.js'; +import FedsLogin from '../feds/login/login.page.js'; +import FedsHeader from '../feds/header/header.page.js'; + +const miloLibs = process.env.MILO_LIBS || ''; + +let COMM; +test.beforeEach(async ({ page, baseURL, browserName }) => { + COMM = new CommercePage(page); + if (browserName === 'chromium') { + await page.setExtraHTTPHeaders({ 'sec-ch-ua': '"Chromium";v="123", "Not:A-Brand";v="8"' }); + } + + const skipOn = ['bacom', 'business']; + skipOn.some((skip) => { + if (baseURL.includes(skip)) test.skip(true, `Skipping the commerce tests for ${baseURL}`); + return null; + }); +}); + +test.describe('Commerce feature test suite', () => { + // @Commerce-Price-Term - Validate price with term display + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[0].path}${miloLibs}`; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Validate regular price display', async () => { + await COMM.price.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.price.innerText()).toContain('US$263.88/yr'); + expect(await COMM.price.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.price.locator('.price-unit-type').innerText()).toBe(''); + expect(await COMM.price.locator('.price-tax-inclusivity').innerText()).toBe(''); + }); + + await test.step('Validate optical price display', async () => { + await COMM.priceOptical.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.priceOptical.innerText()).toContain('US$21.99/mo'); + expect(await COMM.priceOptical.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.priceOptical.locator('.price-unit-type').innerText()).toBe(''); + expect(await COMM.priceOptical.locator('.price-tax-inclusivity').innerText()).toBe(''); + }); + + await test.step('Validate strikethrough price display', async () => { + await COMM.priceStrikethrough.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.priceStrikethrough.innerText()).toContain('US$263.88/yr'); + expect(await COMM.priceStrikethrough.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.priceStrikethrough.locator('.price-unit-type').innerText()).toBe(''); + expect(await COMM.priceStrikethrough.locator('.price-tax-inclusivity').innerText()).toBe(''); + const priceStyle = await COMM.priceStrikethrough.evaluate( + (e) => window.getComputedStyle(e).getPropertyValue('text-decoration'), + ); + expect(await priceStyle).toContain('line-through'); + }); + }); + + // @Commerce-Price-Unit-Term - Validate price with term and unit display + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[1].path}${miloLibs}`; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Validate regular price display', async () => { + await COMM.price.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.price.innerText()).toContain('US$'); + expect(await COMM.price.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.price.locator('.price-unit-type').innerText()).not.toBe(''); + expect(await COMM.price.locator('.price-tax-inclusivity').innerText()).toBe(''); + }); + + await test.step('Validate optical price display', async () => { + await COMM.priceOptical.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.priceOptical.innerText()).toContain('US$'); + expect(await COMM.priceOptical.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.priceOptical.locator('.price-unit-type').innerText()).not.toBe(''); + expect(await COMM.priceOptical.locator('.price-tax-inclusivity').innerText()).toBe(''); + }); + + await test.step('Validate strikethrough price display', async () => { + await COMM.priceStrikethrough.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.priceStrikethrough.innerText()).toContain('US$'); + expect(await COMM.priceStrikethrough.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.priceStrikethrough.locator('.price-unit-type').innerText()).not.toBe(''); + expect(await COMM.priceStrikethrough.locator('.price-tax-inclusivity').innerText()).toBe(''); + const priceStyle = await COMM.priceStrikethrough.evaluate( + (e) => window.getComputedStyle(e).getPropertyValue('text-decoration'), + ); + expect(await priceStyle).toContain('line-through'); + }); + }); + + // @Commerce-Price-Taxlabel-Unit-Term - Validate price with term, unit and tax label display + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[2].path}${miloLibs}`; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Validate regular price display', async () => { + await COMM.price.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.price.innerText()).toContain('US$'); + expect(await COMM.price.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.price.locator('.price-unit-type').innerText()).not.toBe(''); + expect(await COMM.price.locator('.price-tax-inclusivity').innerText()).not.toBe(''); + }); + + await test.step('Validate optical price display', async () => { + await COMM.priceOptical.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.priceOptical.innerText()).toContain('US$'); + expect(await COMM.priceOptical.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.priceOptical.locator('.price-unit-type').innerText()).not.toBe(''); + expect(await COMM.priceOptical.locator('.price-tax-inclusivity').innerText()).not.toBe(''); + }); + + await test.step('Validate strikethrough price display', async () => { + await COMM.priceStrikethrough.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.priceStrikethrough.innerText()).toContain('US$'); + expect(await COMM.priceStrikethrough.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.priceStrikethrough.locator('.price-unit-type').innerText()).not.toBe(''); + expect(await COMM.priceStrikethrough.locator('.price-tax-inclusivity').innerText()).not.toBe(''); + const priceStyle = await COMM.priceStrikethrough.evaluate( + (e) => window.getComputedStyle(e).getPropertyValue('text-decoration'), + ); + expect(await priceStyle).toContain('line-through'); + }); + }); + + // @Commerce-Promo - Validate price and CTAs have promo code applied + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[3].path}${miloLibs}`; + const { data } = features[3]; + + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Validate regular price has promo', async () => { + await COMM.price.waitFor({ state: 'visible', timeout: 10000 }); + await expect(COMM.price).toHaveAttribute('data-promotion-code', data.promo); + await expect(COMM.price).toHaveAttribute('data-display-old-price', 'true'); + await COMM.price.locator('.price').first().waitFor({ state: 'visible', timeout: 10000 }); + await COMM.price.locator('.price-strikethrough').waitFor({ state: 'visible', timeout: 10000 }); + }); + + await test.step('Validate optical price has promo', async () => { + await COMM.priceOptical.waitFor({ state: 'visible', timeout: 10000 }); + await expect(COMM.priceOptical).toHaveAttribute('data-promotion-code', data.promo); + }); + + await test.step('Validate strikethrough price has promo', async () => { + await COMM.priceStrikethrough.waitFor({ state: 'visible', timeout: 10000 }); + await expect(COMM.priceStrikethrough).toHaveAttribute('data-promotion-code', data.promo); + }); + + await test.step('Validate Buy now CTA has promo', async () => { + await COMM.buyNowCta.waitFor({ state: 'visible', timeout: 10000 }); + await expect(COMM.buyNowCta).toHaveAttribute('data-promotion-code', data.promo); + await expect(COMM.buyNowCta).toHaveAttribute('href', new RegExp(`${data.promo}`)); + }); + + await test.step('Validate Free Trial CTA has promo', async () => { + await COMM.freeTrialCta.waitFor({ state: 'visible', timeout: 10000 }); + await expect(COMM.freeTrialCta).toHaveAttribute('data-promotion-code', data.promo); + await expect(COMM.freeTrialCta).toHaveAttribute('href', new RegExp(`${data.promo}`)); + await expect(COMM.freeTrialCta).toHaveAttribute('href', new RegExp(`${data.workflow}`)); + }); + }); + + // @Commerce-Upgrade-Entitlement - Validate Upgrade commerce flow + test(`${features[4].name}, ${features[4].tags}`, async ({ page, baseURL }) => { + test.skip(); // Skipping due to missing login + + const testPage = `${baseURL}${features[4].path}${miloLibs}`; + console.info('[Test Page]: ', testPage); + + const { data } = features[4]; + const Login = new FedsLogin(page); + const Header = new FedsHeader(page); + + // Go to test example + await test.step('Go to test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + // Login with Adobe test account: + await test.step('Login with a valid Adobe account', async () => { + await Header.signInButton.click(); + if (COMM.loginType.isVisible()) { + await COMM.loginType.click(); + } + await Login.loginOnAppForm(process.env.IMS_EMAIL_PAID_PS, process.env.IMS_PASS_PAID_PS); + }); + + // Validate Upgrade eligibility check w.r.t Buy CTA + await test.step('Verify cc all apps card cta title', async () => { + await page.waitForLoadState('domcontentloaded'); + await COMM.ccAllAppsCTA.waitFor({ state: 'visible', timeout: 10000 }); + await expect(COMM.ccAllAppsCTA).toHaveText(data.UpgradeCTATitle); + }); + + // Validate Upgrade eligibility check w.r.t Switch modal + await test.step('Verify Switch modal launch for Upgrade', async () => { + await COMM.ccAllAppsCTA.click(); + await COMM.switchModalIframe.waitFor({ state: 'visible', timeout: 45000 }); + await expect(COMM.switchModalIframe).toBeVisible(); + }); + }); + + // @Commerce-Download-Entitlement - Validate Download commerce flow + test(`${features[5].name}, ${features[5].tags}`, async ({ page, baseURL }) => { + test.skip(); // Skipping due to missing login + + const testPage = `${baseURL}${features[5].path}${miloLibs}`; + console.info('[Test Page]: ', testPage); + const { data } = features[5]; + const Login = new FedsLogin(page); + const Header = new FedsHeader(page); + + // Go to test example + await test.step('Go to test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + // Login with Adobe test account: + await test.step('Login with a valid Adobe account', async () => { + await Header.signInButton.click(); + if (COMM.loginType.isVisible()) { + await COMM.loginType.click(); + } + await Login.loginOnAppForm(process.env.IMS_EMAIL_PAID_PS, process.env.IMS_PASS_PAID_PS); + }); + + // Validate Download eligibility check w.r.t Buy CTA + await test.step('Verify photoshop card cta title', async () => { + await page.waitForLoadState('domcontentloaded'); + await COMM.photoshopBuyCTA.waitFor({ state: 'visible', timeout: 10000 }); + await expect(COMM.photoshopBuyCTA).toHaveText(data.DownloadCTATitle); + await expect(COMM.photoshopFreeCTA).toHaveText(data.TrialCTATitle); + }); + + // Validate Download eligibility check w.r.t download link + await test.step('Verify download link for download', async () => { + await COMM.photoshopBuyCTA.click(); + await page.waitForLoadState('domcontentloaded'); + await expect(page.url()).toContain(data.DownloadUrl); + }); + }); + + // @Commerce-KitchenSink-Smoke - Validate commerce CTA and checkout placeholders + test(`${features[6].name}, ${features[6].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[6].path}${miloLibs}`; + const webUtil = new WebUtil(page); + + console.info('[Test Page]: ', testPage); + + // Go to test example + await test.step('Go to test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + // Validate there are no unresolved commerce placeholders + await test.step('Validate wcs placeholders', async () => { + await COMM.merchCard.first().waitFor({ state: 'visible', timeout: 45000 }); + await webUtil.scrollPage('down', 'slow'); + const unresolvedPlaceholders = await page.evaluate( + () => [...document.querySelectorAll('[data-wcs-osi]')].filter( + (el) => !el.classList.contains('placeholder-resolved'), + ), + ); + expect(unresolvedPlaceholders.length).toBe(0); + }); + + // Validate commerce checkout links are indeed commerce + await test.step('Validate checkout links', async () => { + const invalidCheckoutLinks = await page.evaluate( + () => [...document.querySelectorAll('[data-wcs-osi][is="checkout-link"]')].filter( + (el) => !el.getAttribute('href').includes('commerce'), + ), + ); + expect(invalidCheckoutLinks.length).toBe(0); + }); + }); + + // @Commerce-DE - Validate commerce CTA and checkout placeholders in DE locale + test(`${features[7].name}, ${features[7].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[7].path}${miloLibs}`; + const { data } = features[7]; + + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + // Validate there are no unresolved commerce placeholders + await test.step('Validate wcs placeholders', async () => { + const unresolvedPlaceholders = await page.evaluate( + () => [...document.querySelectorAll('[data-wcs-osi]')].filter( + (el) => !el.classList.contains('placeholder-resolved'), + ), + ); + expect(unresolvedPlaceholders.length).toBe(0); + }); + + await test.step('Validate Buy now CTA', async () => { + await COMM.buyNowCta.waitFor({ state: 'visible', timeout: 10000 }); + await expect(COMM.buyNowCta).toHaveAttribute('data-promotion-code', data.promo); + await expect(COMM.buyNowCta).toHaveAttribute('href', new RegExp(`${data.promo}`)); + await expect(COMM.buyNowCta).toHaveAttribute('href', new RegExp(`${data.CO}`)); + await expect(COMM.buyNowCta).toHaveAttribute('href', new RegExp(`${data.lang}`)); + }); + + await test.step('Validate Free Trial CTA', async () => { + await COMM.freeTrialCta.waitFor({ state: 'visible', timeout: 10000 }); + await expect(COMM.freeTrialCta).toHaveAttribute('data-promotion-code', data.promo); + await expect(COMM.freeTrialCta).toHaveAttribute('href', new RegExp(`${data.promo}`)); + await expect(COMM.freeTrialCta).toHaveAttribute('href', new RegExp(`${data.CO}`)); + await expect(COMM.freeTrialCta).toHaveAttribute('href', new RegExp(`${data.lang}`)); + await expect(COMM.freeTrialCta).toHaveAttribute('href', new RegExp(`${data.workflow}`)); + }); + + await test.step('Validate regular price display', async () => { + await COMM.price.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.price.innerText()).toContain('€/Jahr'); + expect(await COMM.price.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.price.locator('.price-unit-type').innerText()).toBe(''); + expect(await COMM.price.locator('.price-tax-inclusivity').innerText()).toBe(''); + await expect(COMM.price).toHaveAttribute('data-promotion-code', data.promo); + }); + + await test.step('Validate optical price display', async () => { + await COMM.priceOptical.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.priceOptical.innerText()).toContain('€/Monat'); + expect(await COMM.priceOptical.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.priceOptical.locator('.price-unit-type').innerText()).toBe(''); + expect(await COMM.priceOptical.locator('.price-tax-inclusivity').innerText()).toBe(''); + await expect(COMM.priceOptical).toHaveAttribute('data-promotion-code', data.promo); + }); + + await test.step('Validate strikethrough price display', async () => { + await COMM.priceStrikethrough.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.priceStrikethrough.innerText()).toContain('€/Jahr'); + expect(await COMM.priceStrikethrough.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.priceStrikethrough.locator('.price-unit-type').innerText()).toBe(''); + expect(await COMM.priceStrikethrough.locator('.price-tax-inclusivity').innerText()).toBe(''); + const priceStyle = await COMM.priceStrikethrough.evaluate( + (e) => window.getComputedStyle(e).getPropertyValue('text-decoration'), + ); + expect(await priceStyle).toContain('line-through'); + await expect(COMM.priceStrikethrough).toHaveAttribute('data-promotion-code', data.promo); + }); + }); + + // @Commerce-Old-Promo - Validate promo price WITHOUT old price + test(`${features[8].name},${features[8].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[8].path}${miloLibs}`; + const { data } = features[8]; + + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Validate promo price does not show old price', async () => { + await COMM.price.waitFor({ state: 'visible', timeout: 10000 }); + await expect(COMM.price).toHaveAttribute('data-promotion-code', data.promo); + await expect(COMM.price).not.toHaveAttribute('data-display-old-price', 'true'); + // expect(await COMM.price.innerText()).toContain('US$17.24'); + // expect(await COMM.price.innerText()).not.toContain('US$34.49'); + await expect(await COMM.price.locator('.price').first()).toBeVisible(); + await expect(await COMM.price.locator('.price-strikethrough')).not.toBeVisible(); + }); + }); + + // @Commerce-GB - Validate commerce CTA and checkout placeholders in UK locale + test(`${features[9].name}, ${features[9].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[9].path}${miloLibs}`; + const { data } = features[9]; + + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + // Validate there are no unresolved commerce placeholders + await test.step('Validate wcs placeholders', async () => { + const unresolvedPlaceholders = await page.evaluate( + () => [...document.querySelectorAll('[data-wcs-osi]')].filter( + (el) => !el.classList.contains('placeholder-resolved'), + ), + ); + expect(unresolvedPlaceholders.length).toBe(0); + }); + + await test.step('Validate Buy now CTA', async () => { + await COMM.buyNowCta.waitFor({ state: 'visible', timeout: 10000 }); + await expect(COMM.buyNowCta).toHaveAttribute('data-promotion-code', data.promo); + await expect(COMM.buyNowCta).toHaveAttribute('href', new RegExp(`${data.promo}`)); + await expect(COMM.buyNowCta).toHaveAttribute('href', new RegExp(`${data.CO}`)); + await expect(COMM.buyNowCta).toHaveAttribute('href', new RegExp(`${data.lang}`)); + }); + + await test.step('Validate Free Trial CTA', async () => { + await COMM.freeTrialCta.waitFor({ state: 'visible', timeout: 10000 }); + await expect(COMM.freeTrialCta).toHaveAttribute('data-promotion-code', data.promo); + await expect(COMM.freeTrialCta).toHaveAttribute('href', new RegExp(`${data.promo}`)); + await expect(COMM.freeTrialCta).toHaveAttribute('href', new RegExp(`${data.CO}`)); + await expect(COMM.freeTrialCta).toHaveAttribute('href', new RegExp(`${data.lang}`)); + await expect(COMM.freeTrialCta).toHaveAttribute('href', new RegExp(`${data.workflow}`)); + }); + + await test.step('Validate regular price display', async () => { + await COMM.price.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.price.innerText()).toContain('£'); + expect(await COMM.price.innerText()).toContain('/yr'); + expect(await COMM.price.locator('.price-recurrence').first().innerText()).not.toBe(''); + expect(await COMM.price.locator('.price-unit-type').first().innerText()).toBe(''); + expect(await COMM.price.locator('.price-tax-inclusivity').first().innerText()).toBe(''); + await expect(COMM.price).toHaveAttribute('data-promotion-code', data.promo); + await expect(COMM.price).toHaveAttribute('data-display-old-price', 'true'); + await COMM.price.locator('.price').first().waitFor({ state: 'visible', timeout: 10000 }); + await COMM.price.locator('.price-strikethrough').waitFor({ state: 'visible', timeout: 10000 }); + }); + + await test.step('Validate optical price display', async () => { + await COMM.priceOptical.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.priceOptical.innerText()).toContain('£'); + expect(await COMM.priceOptical.innerText()).toContain('/mo'); + expect(await COMM.priceOptical.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.priceOptical.locator('.price-unit-type').innerText()).toBe(''); + expect(await COMM.priceOptical.locator('.price-tax-inclusivity').innerText()).toBe(''); + await expect(COMM.priceOptical).toHaveAttribute('data-promotion-code', data.promo); + }); + + await test.step('Validate strikethrough price display', async () => { + await COMM.priceStrikethrough.waitFor({ state: 'visible', timeout: 10000 }); + expect(await COMM.priceStrikethrough.innerText()).toContain('£'); + expect(await COMM.priceStrikethrough.innerText()).toContain('/yr'); + expect(await COMM.priceStrikethrough.locator('.price-recurrence').innerText()).not.toBe(''); + expect(await COMM.priceStrikethrough.locator('.price-unit-type').innerText()).toBe(''); + expect(await COMM.priceStrikethrough.locator('.price-tax-inclusivity').innerText()).toBe(''); + const priceStyle = await COMM.priceStrikethrough.evaluate( + (e) => window.getComputedStyle(e).getPropertyValue('text-decoration'), + ); + expect(await priceStyle).toContain('line-through'); + await expect(COMM.priceStrikethrough).toHaveAttribute('data-promotion-code', data.promo); + }); + }); +}); diff --git a/nala/features/feds/footer/footer.page.js b/nala/features/feds/footer/footer.page.js new file mode 100644 index 0000000000..cb19320cb9 --- /dev/null +++ b/nala/features/feds/footer/footer.page.js @@ -0,0 +1,77 @@ +/* eslint-disable import/no-import-module-exports */ + +export default class FedsFooter { + constructor(page) { + this.page = page; + + // Container Selectors: + this.footerContainer = page.locator('footer.global-footer'); + this.footerSections = page.locator('footer div.feds-menu-section'); + this.footerColumns = page.locator('footer div.feds-menu-column'); + this.footerHeadings = page.locator('footer div.feds-menu-headline'); + + // Change Region Selectors: + this.changeRegionContainer = page.locator('div.feds-regionPicker-wrapper'); + this.changeRegionButton = page.locator('div.feds-regionPicker-wrapper a.feds-regionPicker'); + this.changeRegionModal = page.locator('div#langnav'); + this.changeRegionDropDown = page.locator('div.region-selector'); + this.changeRegionCloseButton = page.locator('button.dialog-close'); + + // Legal Selectors: + this.legalContainer = page.locator('div.feds-footer-legalWrapper'); + this.legalSections = page.locator('p.feds-footer-privacySection'); + this.legalLinks = page.locator('div.feds-footer-legalWrapper a'); + this.legalCopyright = page.locator('span.feds-footer-copyright'); + this.privacyLink = page.locator('a[href*="privacy.html"]'); + this.termsOfUseLink = page.locator('a[href*="terms.html"]'); + this.cookiePreferencesLink = page.locator('a[href*="#openPrivacy"]'); + this.doNotSellInformationLink = page.locator('a[href*="ca-rights.html"]'); + this.adChoicesLink = page.locator('a[href*="opt-out.html"]'); + this.adChoicesLogo = page.locator('svg.feds-adChoices-icon'); + + // Adobe Socials Selectors: + this.twitterIcon = page.locator('ul.feds-social a[aria-label="twitter"]'); + this.linkedInIcon = page.locator('ul.feds-social a[aria-label="linkedin"]'); + this.facebookIcon = page.locator('ul.feds-social a[aria-label="facebook"]'); + this.instagramIcon = page.locator('ul.feds-social a[aria-label="instagram"]'); + this.socialContainer = page.locator('ul.feds-social'); + this.socialIcons = page.locator('ul.feds-social li'); + + // Featured Products Selectors: + this.featuredProductsContainer = page.locator('div.feds-featuredProducts'); + this.featuredProducts = page.locator('div.feds-featuredProducts a'); + this.downloadAdobeExpress = page.locator('footer a[daa-ll="Adobe_Express"]'); + this.downloadAdobePhotoshop = page.locator('footer a[daa-ll="Photoshop"]'); + this.downloadAdobeIllustrator = page.locator('footer a[daa-ll="Illustrator"]'); + + // Footer Section Selectors: + this.footerCreativeCloud = page.locator(".feds-footer-wrapper a[href*='creativecloud.html']"); + this.footerViewAllProducts = page.locator(".feds-navLink[href*='/products/catalog.html?']"); + this.footerCreativeCloudForBusiness = page.locator(".feds-footer-wrapper [href$='cloud/business.html']").nth(0); + this.footerAcrobatForBusiness = page.locator(".feds-footer-wrapper a[href$='acrobat/business.html']"); + this.footerDiscountsForStudentsAndTeachers = page.locator(".feds-footer-wrapper a[href$='buy/students.html']"); + this.footerDigitalLearningSolutions = page.locator("a[href$='/elearning.html']"); + this.footerAppsforiOS = page.locator("a[href*='id852473028']"); + this.footerAppsforAndroid = page.locator("a[href*='id=com.adobe.cc']"); + this.footerWhatIsExperienceCloud = page.locator('.feds-footer-wrapper a[href*="business"]').nth(4); + this.footerTermsOfUse = page.locator('a[href*="experiencecloudterms"]'); + this.footerDownloadAndInstall = page.locator('.feds-footer-wrapper a[href*="download-install.html"]'); + this.footerGenuineSoftware = page.locator('a[href*="genuine.html"]'); + this.footerAdobeBlog = page.locator('.feds-navLink[href*="blog"]').nth(1); + this.footerAdobeDeveloper = page.locator('a[href*="developer"]'); + this.footerLogInToYourAccount = page.locator('.feds-footer-wrapper a[href*="account.adobe"]').nth(0); + this.footerAbout = page.locator('.feds-footer-wrapper [href*="about-adobe.html"]').nth(0); + this.footerIntegrity = page.locator('a[href*="integrity.html"]'); + this.footerAdobeBlogSecond = page.locator('.feds-navLink[href*="blog"]').nth(0); + this.protectMyPersonalData = page.locator('.feds-footer-legalWrapper a:nth-of-type(4)'); + this.termsOfUseLinkTwo = page.locator('a[href*="terms.html"]').nth(1); + + // Featured Product Selectors: + this.footerAdobeAcrobatReaderlogo = page.locator('a[href$="reader/"]'); + this.footerAdobeExpresslogo = page.locator('a[href$="Z2G1FSYV&mv=other"]:nth-of-type(2)'); + this.footerPhotoshoplogo = page.locator('a[href$="photoshop/free-trial-download.html"]'); + this.footerIllustratorlogo = page.locator('a[href$="illustrator/free-trial-download.html"]'); + } + + // >> FEDS Footer methods declared here << +} diff --git a/nala/features/feds/footer/footer.spec.js b/nala/features/feds/footer/footer.spec.js new file mode 100644 index 0000000000..47db482828 --- /dev/null +++ b/nala/features/feds/footer/footer.spec.js @@ -0,0 +1,26 @@ +module.exports = { + name: 'Footer Block', + features: [ + { + name: '@FEDS-Default-Footer', + path: [ + '/drafts/nala/blocks/footer/feds-default-footer', + ], + tags: '@milo @feds @footer @smoke @regression', + }, + { + name: '@FEDS-Skinny-Footer', + path: [ + '/drafts/nala/blocks/footer/feds-skinny-footer', + ], + tags: '@milo @feds @footer @smoke @regression', + }, + { + name: '@FEDS-Privacy-Footer', + path: [ + '/drafts/nala/blocks/footer/feds-privacy-footer', + ], + tags: '@milo @feds @footer @smoke @regression', + }, + ], +}; diff --git a/nala/features/feds/footer/footer.test.js b/nala/features/feds/footer/footer.test.js new file mode 100644 index 0000000000..4d231b1bd0 --- /dev/null +++ b/nala/features/feds/footer/footer.test.js @@ -0,0 +1,164 @@ +/* eslint-disable import/named, import/no-extraneous-dependencies, max-len, no-console, no-await-in-loop, import/extensions */ +import { expect, test } from '@playwright/test'; +import { features } from './footer.spec.js'; +import FedsFooter from './footer.page.js'; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Footer Block Test Suite', () => { + // FEDS Default Footer Checks: + test(`${features[0].name}, ${features[0].tags}`, async ({ page, baseURL }) => { + const Footer = new FedsFooter(page); + console.info(`[FEDSInfo] Checking page: ${baseURL}${features[0].path}${miloLibs}`); + + await test.step('Navigate to FEDS Default Footer page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('Check FEDS Default Footer critical elements', async () => { + // Scroll FEDS Footer into viewport: + await Footer.legalContainer.scrollIntoViewIfNeeded(); + // Wait for FEDS Footer to be visible: + await Footer.footerContainer.waitFor({ state: 'visible', timeout: 5000 }); + // Check FEDS Footer critical elements: + await expect(Footer.legalContainer).toBeVisible(); + await expect(Footer.socialContainer).toBeVisible(); + await expect(Footer.footerContainer).toBeVisible(); + await expect(Footer.changeRegionContainer).toBeVisible(); + // !Note: Footer featuredProducts not appearing in NALA. Possible BUG! + // await expect(Footer.featuredProductsContainer).toBeVisible(); + await expect(Footer.footerColumns).toHaveCount(5); + + // updated the footer section and heading content as per consuming sites + // milo=6, cc=9 and so on + await expect([4, 6, 9].includes(await Footer.footerSections.count())).toBeTruthy(); + await expect([4, 6, 9].includes(await Footer.footerHeadings.count())).toBeTruthy(); + + await expect(Footer.socialIcons).toHaveCount(4); + await expect(Footer.legalLinks).toHaveCount(5); + }); + + await test.step('Check ChangeRegion functionality', async () => { + await Footer.changeRegionButton.click(); + await expect(Footer.changeRegionModal).toBeVisible(); + await Footer.changeRegionCloseButton.click(); + await expect(Footer.changeRegionModal).not.toBeVisible(); + }); + }); + + // FEDS Skinny Footer Checks: + test(`${features[1].name}, ${features[1].tags}`, async ({ page, baseURL }) => { + const Footer = new FedsFooter(page); + console.info(`[FEDSInfo] Checking page: ${baseURL}${features[1].path}${miloLibs}`); + + await test.step('Navigate to FEDS Skinny Footer page', async () => { + await page.goto(`${baseURL}${features[1].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[1].path}${miloLibs}`); + }); + + await test.step('Check FEDS Skinny Footer critical elements', async () => { + // Scroll FEDS Footer into viewport: + await Footer.legalContainer.scrollIntoViewIfNeeded(); + // Wait for FEDS Footer to be visible: + await Footer.footerContainer.waitFor({ state: 'visible', timeout: 5000 }); + // Check FEDS Footer critical elements: + await expect(Footer.legalContainer).toBeVisible(); + await expect(Footer.socialContainer).toBeVisible(); + await expect(Footer.footerContainer).toBeVisible(); + await expect(Footer.changeRegionContainer).toBeVisible(); + + // await expect(Footer.featuredProducts).toHaveCount(0); + // updated the featuredProducts count as per consuming sites + // milo=0, cc=4 and so on + expect([0, 4].includes(await Footer.featuredProducts.count())).toBeTruthy(); + + const featuredProductsCount = await Footer.featuredProducts.count(); + + if (featuredProductsCount === 0) { + await expect(Footer.featuredProductsContainer).not.toBeVisible(); + } else { + await expect(Footer.featuredProductsContainer).toBeVisible(); + } + + await expect(Footer.legalLinks).toHaveCount(5); + await expect(Footer.socialIcons).toHaveCount(4); + + // await expect(Footer.footerColumns).toHaveCount(0); + // await expect(Footer.footerSections).toHaveCount(0); + // await expect(Footer.footerHeadings).toHaveCount(0); + + const footerSectionsCount = await Footer.featuredProducts.count(); + + if (footerSectionsCount === 0) { + await expect(Footer.footerColumns).not.toBeVisible(); + await expect(Footer.footerSections).not.toBeVisible(); + await expect(Footer.footerHeadings).not.toBeVisible(); + } else { + expect([0, 5].includes(await Footer.footerColumns.count())).toBeTruthy(); + expect([4, 6, 9].includes(await Footer.footerSections.count())).toBeTruthy(); + expect([4, 6, 9].includes(await Footer.footerHeadings.count())).toBeTruthy(); + } + }); + + await test.step('Check ChangeRegion functionality', async () => { + await Footer.changeRegionButton.click(); + await expect(Footer.changeRegionModal).toBeVisible(); + await Footer.changeRegionCloseButton.click(); + await expect(Footer.changeRegionModal).not.toBeVisible(); + }); + }); + + // FEDS Privacy Footer Checks: + test(`${features[2].name}, ${features[2].tags}`, async ({ page, baseURL }) => { + const Footer = new FedsFooter(page); + console.info(`[FEDSInfo] Checking page: ${baseURL}${features[2].path}${miloLibs}`); + + await test.step('Navigate to FEDS Privacy Footer page', async () => { + await page.goto(`${baseURL}${features[2].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[2].path}${miloLibs}`); + }); + + await test.step('Check FEDS Privacy Footer critical elements', async () => { + // Scroll FEDS Footer into viewport: + await Footer.legalContainer.scrollIntoViewIfNeeded(); + // Wait for FEDS Footer to be visible: + await Footer.footerContainer.waitFor({ state: 'visible', timeout: 5000 }); + // Check FEDS Footer critical elements: + await expect(Footer.legalContainer).toBeVisible(); + await expect(Footer.socialContainer).toBeVisible(); + await expect(Footer.footerContainer).toBeVisible(); + await expect(Footer.changeRegionContainer).toBeVisible(); + await expect(Footer.featuredProductsContainer).toBeVisible(); + + await expect(Footer.footerColumns).toHaveCount(5); + + // await expect(Footer.footerSections).toHaveCount(9) + // await expect(Footer.footerHeadings).toHaveCount(9) + // await expect(Footer.featuredProducts).toHaveCount(3); + // await expect(Footer.legalSections).toHaveCount(2); + await expect(Footer.socialIcons).toHaveCount(4); + await expect(Footer.legalLinks).toHaveCount(5); + + // updated the footer section and heading content equal or greater + // than 6, to pass tests on cc pages. + expect([4, 6, 9].includes(await Footer.footerSections.count())).toBeTruthy(); + expect([4, 6, 9].includes(await Footer.footerHeadings.count())).toBeTruthy(); + expect([3, 4].includes(await Footer.featuredProducts.count())).toBeTruthy(); + expect([1, 2].includes(await Footer.legalSections.count())).toBeTruthy(); + expect([4].includes(await Footer.socialIcons.count())).toBeTruthy(); + expect([5].includes(await Footer.legalLinks.count())).toBeTruthy(); + }); + + await test.step('Check ChangeRegion functionality', async () => { + await Footer.changeRegionButton.click(); + await expect(Footer.changeRegionDropDown).toBeVisible(); + await expect(Footer.changeRegionModal).not.toBeVisible(); + await Footer.changeRegionButton.click(); + await expect(Footer.changeRegionDropDown).not.toBeVisible(); + }); + }); +}); diff --git a/nala/features/feds/header/header.page.js b/nala/features/feds/header/header.page.js new file mode 100644 index 0000000000..df43a70215 --- /dev/null +++ b/nala/features/feds/header/header.page.js @@ -0,0 +1,110 @@ +/* eslint-disable import/no-import-module-exports */ +import { expect } from '@playwright/test'; + +export default class FedsHeader { + constructor(page) { + this.page = page; + + // GNAV selectors: + this.gnavLogo = page.locator('a.gnav-logo'); + this.headerContainer = page.locator('header.global-navigation'); + this.mainNavLogo = page.locator('a.feds-brand, a.gnav-brand'); + this.mainNavContainer = page.locator('nav.feds-topnav, .gnav-wrapper'); + this.megaMenuToggle = page.locator('button.feds-navLink.feds-navLink--hoverCaret, .section-menu').first(); + this.megaMenuContainer = page.locator('section.feds-navItem--megaMenu div.feds-popup, .section-menu .gnav-menu-container'); + this.megaMenuColumn = page.locator('section.feds-navItem--megaMenu div.feds-menu-section'); + + // GNAV action selectors: + this.signInButton = page.locator('button.feds-signIn').first(); + this.searchIcon = page.locator('button.feds-search-trigger, button.gnav-search-button'); + this.searchInput = page.locator('input.feds-search-input, input.gnav-search-input'); + this.closeSearch = page.locator('span.feds-search-close, button.gnav-search-button[daa-lh="header|Close"]'); + this.searchResults = page.locator('#feds-search-results, .gnav-search-results'); + this.advancedSearchLink = page.locator('#feds-search-results li a, .gnav-search-results li a'); + + this.profileIcon = page.locator('button.feds-profile-button'); + this.profileModal = page.locator('div#feds-profile-menu'); + this.profileName = page.locator('p.feds-profile-name'); + this.profileEmail = page.locator('p.feds-profile-email'); + this.profileAccountLink = page.locator('p.feds-profile-account'); + this.profileDetails = page.locator('div.feds-profile-details'); + this.profileSignOut = page.locator('a.feds-profile-action'); + + // GNAV breadcrumb selectors: + this.breadcrumbList = page.locator('nav.feds-breadcrumbs ul'); + this.breadcrumbElems = page.locator('nav.feds-breadcrumbs li'); + this.breadcrumbContainer = page.locator('nav.feds-breadcrumbs'); + + // Promo-bar selectors: + this.promoBarContainer = page.locator('div.aside.promobar'); + this.promoBarBackground = this.promoBarContainer.locator('div.background'); + this.promoBarForeground = this.promoBarContainer.locator('div.foreground'); + this.promoBarContent = this.promoBarContainer.locator('div.desktop-up'); + this.promoBarText = this.promoBarContainer.locator('div.desktop-up p.content-area'); + this.promoBarBtn = this.promoBarContainer.locator('div.desktop-up p.action-area a'); + this.promoBarMobileContent = this.promoBarContainer.locator('div.mobile-up'); + this.promoBarMobileText = this.promoBarContainer.locator('div.mobile-up p.content-area'); + this.promoBarMobileBtn = this.promoBarContainer.locator('div.mobile-up p.action-area a'); + this.promoBarTabletContent = this.promoBarContainer.locator('div.tablet-up'); + this.promoBarTabletText = this.promoBarContainer.locator('div.tablet-up p.content-area'); + this.promoBarTabletBtn = this.promoBarContainer.locator('div.tablet-up p.action-area a'); + } + + /** + * Opens the User Profile via click on GNAV profile icon. + * !Note: Only use after user was logged in! + * @param {none} + * @return {Promise} PlayWright promise + */ + async openUserProfile() { + await this.profileIcon.waitFor({ state: 'visible', timeout: 10000 }); + await this.profileIcon.click(); + await expect(this.profileModal).toBeVisible(); + } + + /** + * Closes the User Profile via click on GNAV profile icon. + * !Note: Only use after user was logged in! + * @param {none} + * @return {Promise} PlayWright promise + */ + async closeUserProfile() { + await this.profileIcon.waitFor({ state: 'visible', timeout: 10000 }); + await this.profileIcon.click(); + await expect(this.profileModal).not.toBeVisible(); + } + + /** + * Checks the elements of the User Profile component. + * @param {none} + * @return {Promise} PlayWright promise + */ + async checkUserProfile() { + await expect(this.profileName).toBeVisible(); + await expect(this.profileEmail).toBeVisible(); + await expect(this.profileSignOut).toBeVisible(); + await expect(this.profileAccountLink).toBeVisible(); + } + + /** + * Opens the search bar via click fron GNAV search icon. + * @param {none} + * @return {Promise} PlayWright promise + */ + async openSearchBar() { + await this.searchIcon.waitFor({ state: 'visible', timeout: 10000 }); + await this.searchIcon.click(); + await expect(this.searchInput).toBeVisible(); + } + + /** + * Closes the search bar via click fron GNAV search icon. + * @param {none} + * @return {Promise} PlayWright promise + */ + async closeSearchBar() { + await this.closeSearch.waitFor({ state: 'visible', timeout: 10000 }); + await this.closeSearch.click(); + await expect(this.searchInput).not.toBeVisible(); + } +} diff --git a/nala/features/feds/header/header.spec.js b/nala/features/feds/header/header.spec.js new file mode 100644 index 0000000000..d0e754d39f --- /dev/null +++ b/nala/features/feds/header/header.spec.js @@ -0,0 +1,12 @@ +module.exports = { + name: 'Header Block', + features: [ + { + name: '@FEDS-Header-Checks', + path: [ + '/drafts/nala/blocks/header/feds-header-page', + ], + tags: '@milo @feds @header @nopr @smoke @regression', + }, + ], +}; diff --git a/nala/features/feds/header/header.test.js b/nala/features/feds/header/header.test.js new file mode 100644 index 0000000000..8d1faa58da --- /dev/null +++ b/nala/features/feds/header/header.test.js @@ -0,0 +1,52 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console, no-await-in-loop, import/extensions */ +import { expect, test } from '@playwright/test'; +import { features } from './header.spec.js'; +import FedsHeader from './header.page.js'; + +const miloLibs = process.env.MILO_LIBS || ''; + +test.describe('Header Block Test Suite', () => { + // FEDS Default Header Checks: + test(`${features[0].name}, ${features[0].tags}`, async ({ page, baseURL }) => { + const Header = new FedsHeader(page); + console.info(`[FEDSInfo] Checking page: ${baseURL}${features[0].path}${miloLibs}`); + + await test.step('Navigate to FEDS HEADER page', async () => { + await page.goto(`${baseURL}${features[0].path}${miloLibs}`); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(`${baseURL}${features[0].path}${miloLibs}`); + }); + + await test.step('Check HEADER block content', async () => { + // Wait for FEDS GNAV to be visible: + await Header.mainNavContainer.waitFor({ state: 'visible', timeout: 5000 }); + // Check HEADER block content: + await expect(Header.mainNavLogo).toBeVisible(); + + // skipping the step for PR branch runs + // working on better workaround soloution + // await expect(Header.signInButton).toBeVisible(); + }); + + await test.step('Check HEADER search component', async () => { + // adding the below check to accommodate testing on consuming sites + const isSearchIconVisible = await Header.searchIcon.isVisible(); + if (isSearchIconVisible) { + await test.step('Check HEADER search component', async () => { + await Header.openSearchBar(); + await Header.closeSearchBar(); + }); + } else { + console.info('Search icon is not visible, skipping the search component test.'); + } + }); + + await test.step('Check HEADER block mega menu component', async () => { + await Header.megaMenuToggle.waitFor({ state: 'visible', timeout: 5000 }); + await Header.megaMenuToggle.click(); + await expect(Header.megaMenuContainer).toBeVisible(); + await Header.megaMenuToggle.click(); + await expect(Header.megaMenuContainer).not.toBeVisible(); + }); + }); +}); diff --git a/nala/features/feds/login/login.page.js b/nala/features/feds/login/login.page.js new file mode 100644 index 0000000000..e2baaee3e4 --- /dev/null +++ b/nala/features/feds/login/login.page.js @@ -0,0 +1,118 @@ +// eslint-disable-next-line import/no-import-module-exports +import { expect } from '@playwright/test'; + +export default class FedsLogin { + constructor(page) { + this.page = page; + + this.loginButton = page.locator('button#sign_in'); + this.loginForm = page.locator('form#adobeid_signin'); + this.emailField = page.locator('input#adobeid_username'); + this.passwordField = page.locator('input#adobeid_password'); + + this.loggedInState = page.locator('img.feds-profile-img'); + this.loginWithEnterpriseId = page.locator('a#enterprise_signin_link'); + this.forgotPasswordLink = page.locator('a#adobeid_trouble_signing_in'); + + this.loginWithFacebook = page.locator('a.mod-facebook'); + this.loginWithGoogle = page.locator('a.mod-google'); + this.loginWithApple = page.locator('a.mod-apple'); + + this.appEmailForm = page.locator('form#EmailForm'); + this.appPasswordForm = page.locator('form#PasswordForm'); + this.appEmailField = page.locator('input#EmailPage-EmailField'); + this.appPasswordField = page.locator('input#PasswordPage-PasswordField'); + this.appVisibilityToggle = page.locator('button.PasswordField-VisibilityToggle'); + this.appPasswordContinue = page.locator('button[data-id^="EmailPage"]'); + this.appLoginContinue = page.locator('button[data-id^="PasswordPage"]'); + this.personalAccountLogo = page.locator('img[alt="Personal Account"]'); + this.selectAccountForm = page.locator('div[data-id="Profile"]'); + + this.appEmailFieldSelector = page.locator('input#EmailPage-EmailField'); + this.appPasswordFieldSelector = page.locator('input#PasswordPage-PasswordField'); + this.codePadChallenge = page.locator('div[data-id="ChallengeCodePage"]'); + } + + /** + * Login on the IMS APP login form with email & password. + * @param {string} email + * @param {string} password + * @return {Promise} PlayWright promise + */ + async loginOnAppForm(email, password) { + console.info('[EuroLogin] APP login form identified!'); + // Check EMAIL & PASSWWORD status: + expect(process.env.IMS_EMAIL, 'ERROR: No environment variable found for IMS_EMAIL').toBeTruthy(); + expect(process.env.IMS_PASS, 'ERROR: No environment variable found for IMS_PASS.').toBeTruthy(); + console.info(`[EuroLogin] Logging in with '${email}' account ...`); + // Wait for page to load & stabilize: + await this.page.waitForLoadState('domcontentloaded'); + // Wait for the SUSI login form to load: + await this.appEmailForm.waitFor({ state: 'visible', timeout: 15000 }); + // Insert account email & click 'Continue': + await this.appEmailField.waitFor({ state: 'visible', timeout: 15000 }); + await this.appEmailField.fill(email); + await this.appPasswordContinue.waitFor({ state: 'visible', timeout: 15000 }); + await expect(this.appPasswordContinue).toHaveText('Continue'); + await this.appPasswordContinue.click(); + // Wait for page to load & stabilize: + await this.page.waitForTimeout(5000); + // Insert account password & click 'Continue': + // await this.appPasswordForm.waitFor({state: 'visible', timeout: 15000}); + await this.appPasswordField.waitFor({ state: 'visible', timeout: 15000 }); + await this.appPasswordField.fill(password); + await this.appLoginContinue.waitFor({ state: 'visible', timeout: 15000 }); + await expect(this.appLoginContinue).toHaveText('Continue'); + await this.appLoginContinue.click(); + // Check if login process was successful: + await this.loggedInState.waitFor({ state: 'visible', timeout: 20000 }); + console.info(`[EuroLogin] Successfully logged-in as '${email}' (via APP login form).`); + } + + /** + * Login on the IMS SUSI login form with email & password. + * @param {string} email + * @param {string} password + * @return {Promise} PlayWright promise + */ + async loginOnSusiForm(email, password) { + console.info('[EuroLogin] SUSI login form identified!'); + // Check EMAIL & PASSWWORD status: + expect(process.env.IMS_EMAIL, 'ERROR: No environment variable found for IMS_EMAIL').toBeTruthy(); + expect(process.env.IMS_PASS, 'ERROR: No environment variable found for IMS_PASS.').toBeTruthy(); + console.info(`[EuroLogin] Logging in with '${email}' account ...`); + // Wait for page to load & stabilize: + await this.page.waitForLoadState('networkidle'); + // Wait for the SUSI login form to load: + await this.loginForm.waitFor({ state: 'visible', timeout: 15000 }); + await this.emailField.fill(email); + // !Note: Email field has short client-side validation (load). + // Password field is not interactable during that time. + await this.page.keyboard.press('Tab'); + // Wait for page to load & stabilize: + await this.page.waitForLoadState('domcontentloaded'); + // Set password & click 'Continue': + await this.appPasswordForm.waitFor({ state: 'visible', timeout: 15000 }); + await this.passwordField.waitFor({ state: 'visible', timeout: 15000 }); + await this.passwordField.fill(password); + // Complete the login flow: + await this.loginButton.waitFor({ state: 'visible', timeout: 15000 }); + await this.loginButton.click(); + // Check if login process was successful: + await this.loggedInState.waitFor({ state: 'visible', timeout: 20000 }); + console.info(`[EuroLogin] Successfully logged-in as '${email}' (via SUSI login form).`); + } + + /** + * Toggles the visibility of the IMS password field. + * @param {string} password + * @return {Promise} PlayWright promise + */ + async TogglePasswordVisibility(password) { + await this.appVisibilityToggle.waitFor({ state: 'visible', timeout: 15000 }); + await this.appVisibilityToggle.click(); + await expect(this.appPasswordField).toContain(password); + await this.appVisibilityToggle.click(); + await this.appVisibilityToggle.waitFor({ state: 'visible', timeout: 15000 }); + } +} diff --git a/nala/features/imslogin/imslogin.page.js b/nala/features/imslogin/imslogin.page.js new file mode 100644 index 0000000000..c5a4f2d9a0 --- /dev/null +++ b/nala/features/imslogin/imslogin.page.js @@ -0,0 +1,23 @@ +module.exports = { + '@gnav-signin': '.gnav-signin', + '@gnav-profile-button': '.gnav-profile-button', + '@gnav-signout': 'text=Sign Out', + '@gnav-viewaccount': '.gnav-profile-header', + '@gnav-manageTeam': 'text=Manage Team', + '@email': '#EmailPage-EmailField', + '@password': '#PasswordPage-PasswordField', + '@email-continue-btn': '[data-id=EmailPage-ContinueButton]', + '@verify-continue-btn': '[data-id=Page-PrimaryButton]', + '@password-reset': 'text=Reset your password', + '@password-continue-btn': '[data-id=PasswordPage-ContinueButton]', + '@apple-signin': '[data-id=EmailPage-AppleSignInButton]', + '@google-signin': '[data-id=EmailPage-GoogleSignInButton]', + '@facebook-signin': '[data-id=EmailPage-FacebookSignInButton]', + '@page-heading': '.spectrum-Heading1', + '@gnav-ec-signin': '[daa-ll=Experience_Cloud-1]', + '@gnav-comm-signin': '[daa-ll=Commerce__Magento-2]', + '@gnav-multi-signin': '[daa-ll=Adobe_Account-5]', + '@gnav-app-launcher': '.gnav-applications-button', + '@cc-app-launcher': '#navmenu-apps >> ul >> li:nth-child(1) >> a', + '@app-launcher-list': '.apps >> li', +}; diff --git a/nala/features/osttools/ost.page.js b/nala/features/osttools/ost.page.js new file mode 100644 index 0000000000..68d7586370 --- /dev/null +++ b/nala/features/osttools/ost.page.js @@ -0,0 +1,31 @@ +export default class OSTPage { + constructor(page) { + this.page = page; + + this.searchField = page.locator('//input[contains(@data-testid,"search")]'); + this.productList = page.locator('//span[contains(@class,"productName")]'); + this.planType = page.locator( + '//button/span[contains(@class, "spectrum-Dropdown-label") and (.//ancestor::div/span[contains(text(),"plan type")])]', + ); + this.offerType = page.locator( + '//button/span[contains(@class, "spectrum-Dropdown-label") and (.//ancestor::div/span[contains(text(),"offer type")])]', + ); + this.nextButton = page.locator('//button[contains(@data-testid, "nextButton")]/span'); + this.price = page.locator('//div[@data-type="price"]/span'); + this.priceOptical = page.locator('//div[contains(@data-type, "priceOptical")]/span'); + this.priceStrikethrough = page.locator('//div[contains(@data-type, "priceStrikethrough")]/span'); + this.termCheckbox = page.locator('//input[@value="displayRecurrence"]'); + this.unitCheckbox = page.locator('//input[@value="displayPerUnit"]'); + this.taxlabelCheckbox = page.locator('//input[@value="displayTax"]'); + this.taxInlcusivityCheckbox = page.locator('//input[@value="forceTaxExclusive"]'); + this.oldPrice = page.locator('//input[@value="displayOldPrice"]'); + this.priceUse = page.locator('button:near(h4:text("Price"))').first(); + this.priceOpticalUse = page.locator('button:near(:text("Optical price"))').first(); + this.priceStrikethroughUse = page.locator('button:near(:text("Strikethrough price"))').first(); + this.checkoutTab = page.locator('//div[@data-key="checkout"]'); + this.checkoutLink = page.locator('//a[@data-type="checkoutUrl"]'); + this.workflowMenu = page.locator('button:near(label:text("Workflow"))').first(); + this.promoField = page.locator('//input[contains(@class, "spectrum-Textfield-input")]'); + this.cancelPromo = page.locator('button:right-of(span:text("Promotion:"))').first(); + } +} diff --git a/nala/features/osttools/ost.spec.js b/nala/features/osttools/ost.spec.js new file mode 100644 index 0000000000..fa8001bbf6 --- /dev/null +++ b/nala/features/osttools/ost.spec.js @@ -0,0 +1,128 @@ +module.exports = { + name: 'Offer Selector Tool', + features: [ + { + tcid: '0', + name: '@OST-Search-OfferID', + path: '/tools/ost', + data: { + offerID: '0ADF92A6C8514F2800BE9E87DB641D2A', + productName: 'Photoshop', + productNameShort: 'phsp', + planType: 'PUF', + offerType: 'TRIAL', + price: 'US$263.88', + opticalPrice: 'US$21.99', + term: '/yr', + opticalTerm: '/mo', + unit: 'per license', + taxLabel: 'excl. tax', + }, + browserParams: '?token=', + tags: '@ost @commerce @regression @nopr @jk', + }, + { + tcid: '1', + name: '@OST-Offer-Entitlements', + path: '/tools/ost', + data: { + offerID: '0ADF92A6C8514F2800BE9E87DB641D2A', + planType: 'PUF', + offerType: 'TRIAL', + }, + browserParams: '?token=', + tags: '@ost @commerce @regression @nopr', + + }, + { + tcid: '2', + name: '@OST-Offer-Price', + path: '/tools/ost', + data: { + offerID: '0ADF92A6C8514F2800BE9E87DB641D2A', + price: 'US$263.88', + opticalPrice: 'US$21.99', + term: '/yr', + opticalTerm: '/mo', + unit: 'per license', + taxLabel: 'excl. tax', + }, + browserParams: '?token=', + tags: '@ost @commerce @regression @nopr', + }, + { + tcid: '3', + name: '@OST-Term', + path: '/tools/ost', + data: { + offerID: '0ADF92A6C8514F2800BE9E87DB641D2A', + term: '/yr', + opticalTerm: '/mo', + }, + browserParams: '?token=', + tags: '@ost @commerce @regression @nopr', + }, + { + tcid: '4', + name: '@OST-Unit', + path: '/tools/ost', + data: { + offerID: '0ADF92A6C8514F2800BE9E87DB641D2A', + unit: 'per license', + }, + browserParams: '?token=', + tags: '@ost @commerce @regression @nopr', + }, + { + tcid: '5', + name: '@OST-TaxLabel', + path: '/tools/ost', + data: { + offerID: '0ADF92A6C8514F2800BE9E87DB641D2A', + taxLabel: 'excl. tax', + }, + browserParams: '?token=', + tags: '@ost @commerce @regression @nopr', + }, + { + tcid: '6', + name: '@OST-TaxInclusivity', + path: '/tools/ost', + data: { offerID: '0ADF92A6C8514F2800BE9E87DB641D2A' }, + browserParams: '?token=', + tags: '@ost @commerce @regression @nopr', + }, + { + tcid: '7', + name: '@OST-Price-Promo', + path: '/tools/ost', + data: { + offerID: '0ADF92A6C8514F2800BE9E87DB641D2A', + promo: 'testpromo', + }, + browserParams: '?token=', + tags: '@ost @commerce @regression @nopr', + }, + { + tcid: '8', + name: '@OST-Checkout-Link', + path: '/tools/ost', + data: { + offerID: '0ADF92A6C8514F2800BE9E87DB641D2A', + workflowStep_1: 'email', + workflowStep_2: 'recommendation', + promo: 'testpromo', + }, + browserParams: '?token=', + tags: '@ost @commerce @regression @nopr', + }, + { + tcid: '9', + name: '@OST-OldPrice', + path: '/tools/ost', + data: { offerID: '0ADF92A6C8514F2800BE9E87DB641D2A' }, + browserParams: '?token=', + tags: '@ost @commerce @f1 S@regression @nopr', + }, + ], +}; diff --git a/nala/features/osttools/ost.test.js b/nala/features/osttools/ost.test.js new file mode 100644 index 0000000000..dc87a39aac --- /dev/null +++ b/nala/features/osttools/ost.test.js @@ -0,0 +1,695 @@ +import { expect, test } from '@playwright/test'; +import { features } from './ost.spec.js'; +import OSTPage from './ost.page.js'; +import ims from '../../libs/imslogin.js'; + +let authToken; +let adobeIMS; +let OST; + +test.beforeAll(async ({ browser }) => { + test.slow(); + // Skip tests on github actions and PRs, run only on Jenkins + if (process.env.GITHUB_ACTIONS) test.skip(); + + const page = await browser.newPage(); + await page.goto('https://www.adobe.com/creativecloud/plans.html?mboxDisable=1&adobe_authoring_enabled=true'); + const signinBtn = page.locator('#universal-nav button.profile-comp').first(); + await expect(signinBtn).toBeVisible(); + await signinBtn.click(); + await page.waitForURL('**/auth.services.adobe.com/en_US/index.html**/'); + features[0].url = 'https://www.adobe.com/creativecloud/plans.html?mboxDisable=1&adobe_authoring_enabled=true'; + await ims.fillOutSignInForm(features[0], page); + await expect(async () => { + const response = await page.request.get(features[0].url); + expect(response.status()).toBe(200); + }).toPass(); + authToken = await page.evaluate(() => adobeIMS.getAccessToken().token); +}); + +test.beforeEach(async ({ page }) => { + OST = new OSTPage(page); +}); + +test.describe('OST page test suite', () => { + // Verify OST search by offer ID + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[0].path}`); + + const testPage = `${baseURL}${features[0].path}${features[0].browserParams}${authToken}`; + const { data } = features[0]; + + await test.step('Open Offer Selector Tool', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Enter Offer ID in the search field', async () => { + await OST.searchField.fill(data.offerID); + await page.waitForTimeout(2000); + }); + + await test.step('Validate search results', async () => { + await OST.productList.first().waitFor({ state: 'visible', timeout: 10000 }); + const skus = OST.productList; + expect(await skus.count()).toBeLessThanOrEqual(2); + expect(await skus.nth(0).innerText()).toContain(data.productName); + expect(await skus.nth(1).innerText()).toContain(data.productNameShort); + }); + }); + + // Verify OST offer entitlements + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[1].path}`); + + const testPage = `${baseURL}${features[1].path}${features[1].browserParams}${authToken}`; + const { data } = features[1]; + + await test.step('Open Offer Selector Tool', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Enter Offer ID in the search field', async () => { + await OST.searchField.fill(data.offerID); + await page.waitForTimeout(2000); + }); + + await test.step('Validate entitlements', async () => { + await OST.planType.waitFor({ state: 'visible', timeout: 10000 }); + await OST.offerType.waitFor({ state: 'visible', timeout: 10000 }); + expect(await OST.planType.innerText()).toContain(data.planType); + expect(await OST.offerType.innerText()).toContain(data.offerType); + }); + }); + + // Verify OST offer price options display + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[2].path}`); + + const testPage = `${baseURL}${features[2].path}${features[2].browserParams}${authToken}`; + const { data } = features[2]; + + let clipboardText; + + await test.step('Open Offer Selector Tool', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Enter Offer ID in the search field', async () => { + await OST.searchField.fill(data.offerID); + }); + + await test.step('Click Next button in OST', async () => { + await OST.nextButton.waitFor({ state: 'visible', timeout: 10000 }); + await OST.nextButton.click(); + }); + + await test.step('Validate Offer regular price option', async () => { + await OST.price.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceUse.waitFor({ state: 'visible', timeout: 10000 }); + + expect(await OST.price.innerText()).toContain(data.price); + expect(await OST.price.innerText()).toContain(data.term); + expect(await OST.price.innerText()).toContain(data.unit); + expect(await OST.price.innerText()).not.toContain(data.taxLabel); + + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('milo.adobe.com/tools/ost'); + expect(await clipboardText).toContain('type=price'); + }); + + await test.step('Validate Offer optical price option', async () => { + await OST.priceOptical.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceOpticalUse.waitFor({ state: 'visible', timeout: 10000 }); + + expect(await OST.priceOptical.innerText()).toContain(data.opticalPrice); + expect(await OST.priceOptical.innerText()).toContain(data.opticalTerm); + expect(await OST.priceOptical.innerText()).toContain(data.unit); + expect(await OST.priceOptical.innerText()).not.toContain(data.taxLabel); + + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('milo.adobe.com/tools/ost'); + expect(await clipboardText).toContain('type=priceOptical'); + }); + + await test.step('Validate Offer strikethrough price option', async () => { + await OST.priceStrikethrough.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceStrikethroughUse.waitFor({ state: 'visible', timeout: 10000 }); + + expect(await OST.priceStrikethrough.innerText()).toContain(data.price); + expect(await OST.priceStrikethrough.innerText()).toContain(data.term); + expect(await OST.priceStrikethrough.innerText()).toContain(data.unit); + expect(await OST.priceStrikethrough.innerText()).not.toContain(data.taxLabel); + + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('milo.adobe.com/tools/ost'); + expect(await clipboardText).toContain('type=priceStrikethrough'); + }); + }); + + // Verify OST enebalement for price term text + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[3].path}`); + + const testPage = `${baseURL}${features[3].path}${features[3].browserParams}${authToken}`; + const { data } = features[3]; + + let clipboardText; + + await test.step('Open Offer Selector Tool', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Enter Offer ID in the search field', async () => { + await OST.searchField.fill(data.offerID); + }); + + await test.step('Click Next button in OST', async () => { + await OST.nextButton.waitFor({ state: 'visible', timeout: 10000 }); + await OST.nextButton.click(); + }); + + await test.step('Validate term enablement', async () => { + await OST.price.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceOptical.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceStrikethrough.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceUse.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceOpticalUse.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceStrikethroughUse.waitFor({ state: 'visible', timeout: 10000 }); + + expect(await OST.price.innerText()).toContain(data.term); + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=price'); + expect(await clipboardText).not.toContain('term='); + + expect(await OST.priceOptical.innerText()).toContain(data.opticalTerm); + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=priceOptical'); + expect(await clipboardText).not.toContain('term='); + + expect(await OST.priceStrikethrough.innerText()).toContain(data.term); + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=priceStrikethrough'); + expect(await clipboardText).not.toContain('term='); + + // Check term checkbox + await OST.termCheckbox.click(); + + expect(await OST.price.innerText()).not.toContain(data.term); + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('term=false'); + + expect(await OST.priceOptical.innerText()).not.toContain(data.opticalTerm); + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('term=false'); + + expect(await OST.priceStrikethrough.innerText()).not.toContain(data.term); + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('term=false'); + + // Uncheck term checkbox + await OST.termCheckbox.click(); + + expect(await OST.price.innerText()).toContain(data.term); + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('term='); + + expect(await OST.priceOptical.innerText()).toContain(data.opticalTerm); + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('term='); + + expect(await OST.priceStrikethrough.innerText()).toContain(data.term); + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('term='); + }); + }); + + // Verify OST enebalement for price unit text + test(`${features[4].name},${features[4].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[4].path}`); + + const testPage = `${baseURL}${features[4].path}${features[4].browserParams}${authToken}`; + const { data } = features[4]; + + let clipboardText; + + await test.step('Open Offer Selector Tool', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Enter Offer ID in the search field', async () => { + await OST.searchField.fill(data.offerID); + }); + + await test.step('Click Next button in OST', async () => { + await OST.nextButton.waitFor({ state: 'visible', timeout: 10000 }); + await OST.nextButton.click(); + }); + + await test.step('Validate unit enablement', async () => { + await OST.price.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceOptical.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceStrikethrough.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceUse.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceOpticalUse.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceStrikethroughUse.waitFor({ state: 'visible', timeout: 10000 }); + + expect(await OST.price.innerText()).toContain(data.unit); + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=price'); + expect(await clipboardText).not.toContain('seat='); + + expect(await OST.priceOptical.innerText()).toContain(data.unit); + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=priceOptical'); + expect(await clipboardText).not.toContain('seat='); + + expect(await OST.priceStrikethrough.innerText()).toContain(data.unit); + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=priceStrikethrough'); + expect(await clipboardText).not.toContain('seat='); + + // Check unit checkbox + await OST.unitCheckbox.click(); + + expect(await OST.price.innerText()).not.toContain(data.unit); + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('seat=false'); + + expect(await OST.priceOptical.innerText()).not.toContain(data.unit); + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('seat=false'); + + expect(await OST.priceStrikethrough.innerText()).not.toContain(data.unit); + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('seat=false'); + + // Uncheck unit checkbox + await OST.unitCheckbox.click(); + + expect(await OST.price.innerText()).toContain(data.unit); + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('seat='); + + expect(await OST.priceOptical.innerText()).toContain(data.unit); + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('seat='); + + expect(await OST.priceStrikethrough.innerText()).toContain(data.unit); + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('seat='); + }); + }); + + // Verify OST enebalement for price tax label + test(`${features[5].name},${features[5].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[5].path}`); + + const testPage = `${baseURL}${features[5].path}${features[5].browserParams}${authToken}`; + const { data } = features[5]; + + let clipboardText; + + await test.step('Open Offer Selector Tool', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Enter Offer ID in the search field', async () => { + await OST.searchField.fill(data.offerID); + }); + + await test.step('Click Next button in OST', async () => { + await OST.nextButton.waitFor({ state: 'visible', timeout: 10000 }); + await OST.nextButton.click(); + }); + + await test.step('Validate tax label enablement', async () => { + await OST.price.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceOptical.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceStrikethrough.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceUse.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceOpticalUse.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceStrikethroughUse.waitFor({ state: 'visible', timeout: 10000 }); + + expect(await OST.price.innerText()).not.toContain(data.taxLabel); + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=price'); + expect(await clipboardText).not.toContain('tax='); + + expect(await OST.priceOptical.innerText()).not.toContain(data.taxLabel); + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=priceOptical'); + expect(await clipboardText).not.toContain('tax='); + + expect(await OST.priceStrikethrough.innerText()).not.toContain(data.taxLabel); + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=priceStrikethrough'); + expect(await clipboardText).not.toContain('tax='); + + // Check tax label checkbox + await OST.taxlabelCheckbox.click(); + + expect(await OST.price.innerText()).toContain(data.taxLabel); + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('tax=true'); + + expect(await OST.priceOptical.innerText()).toContain(data.taxLabel); + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('tax=true'); + + expect(await OST.priceStrikethrough.innerText()).toContain(data.taxLabel); + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('tax=true'); + + // Uncheck tax label checkbox + await OST.taxlabelCheckbox.click(); + + expect(await OST.price.innerText()).not.toContain(data.taxLabel); + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('tax='); + + expect(await OST.priceOptical.innerText()).not.toContain(data.taxLabel); + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('tax='); + + expect(await OST.priceStrikethrough.innerText()).not.toContain(data.taxLabel); + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('tax='); + }); + }); + + // Verify OST enebalement for tax inclusivity in the price + test(`${features[6].name},${features[6].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[6].path}`); + + const testPage = `${baseURL}${features[6].path}${features[6].browserParams}${authToken}`; + const { data } = features[6]; + + let clipboardText; + + await test.step('Open Offer Selector Tool', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Enter Offer ID in the search field', async () => { + await OST.searchField.fill(data.offerID); + }); + + await test.step('Click Next button in OST', async () => { + await OST.nextButton.waitFor({ state: 'visible', timeout: 10000 }); + await OST.nextButton.click(); + }); + + await test.step('Validate tax inclusivity enablement', async () => { + await OST.price.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceOptical.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceStrikethrough.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceUse.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceOpticalUse.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceStrikethroughUse.waitFor({ state: 'visible', timeout: 10000 }); + + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=price'); + expect(await clipboardText).not.toContain('exclusive='); + + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=priceOptical'); + expect(await clipboardText).not.toContain('exclusive='); + + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=priceStrikethrough'); + expect(await clipboardText).not.toContain('exclusive='); + + // Check tax label checkbox + await OST.taxInlcusivityCheckbox.click(); + + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('exclusive=true'); + + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('exclusive=true'); + + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('exclusive=true'); + + // Uncheck tax label checkbox + await OST.taxInlcusivityCheckbox.click(); + + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('exclusive='); + + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('exclusive='); + + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('exclusive='); + }); + }); + + // Verify OST offer price promo + test(`${features[7].name},${features[7].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[7].path}`); + + const testPage = `${baseURL}${features[7].path}${features[7].browserParams}${authToken}`; + const { data } = features[7]; + + let clipboardText; + + await test.step('Open Offer Selector Tool', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Enter Offer ID in the search field', async () => { + await OST.searchField.fill(data.offerID); + }); + + await test.step('Click Next button in OST', async () => { + await OST.nextButton.waitFor({ state: 'visible', timeout: 10000 }); + await OST.nextButton.click(); + }); + + await test.step('Validate price with promo option', async () => { + await OST.price.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceUse.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceOptical.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceOpticalUse.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceStrikethrough.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceStrikethroughUse.waitFor({ state: 'visible', timeout: 10000 }); + await OST.promoField.waitFor({ state: 'visible', timeout: 10000 }); + await OST.cancelPromo.waitFor({ state: 'visible', timeout: 10000 }); + + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('promo='); + + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('promo='); + + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('promo='); + + // Add promo + await OST.promoField.fill(data.promo); + + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain(`promo=${data.promo}`); + + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain(`promo=${data.promo}`); + + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain(`promo=${data.promo}`); + + // Cancel promo + await OST.cancelPromo.click(); + + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('promo='); + + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('promo='); + + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('promo='); + }); + }); + + // Verify OST checkout link generation + test(`${features[8].name},${features[8].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[8].path}`); + + const testPage = `${baseURL}${features[7].path}${features[8].browserParams}${authToken}`; + const { data } = features[8]; + + await test.step('Open Offer Selector Tool', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Enter Offer ID in the search field', async () => { + await OST.searchField.fill(data.offerID); + }); + + await test.step('Click Next button in OST', async () => { + await OST.nextButton.waitFor({ state: 'visible', timeout: 10000 }); + await OST.nextButton.click(); + }); + + await test.step('Go to Checkout link tab', async () => { + await OST.checkoutTab.waitFor({ state: 'visible', timeout: 10000 }); + await OST.checkoutTab.click(); + }); + + await test.step('Validate Checkout Link', async () => { + await OST.checkoutLink.waitFor({ state: 'visible', timeout: 10000 }); + await OST.promoField.waitFor({ state: 'visible', timeout: 10000 }); + await OST.workflowMenu.waitFor({ state: 'visible', timeout: 10000 }); + + await expect(OST.checkoutLink).toHaveAttribute('href', new RegExp(`${data.offerID}`)); + await expect(OST.checkoutLink).toHaveAttribute('href', new RegExp(`${data.workflowStep_1}`)); + await expect(OST.checkoutLink).not.toHaveAttribute('href', /apc=/); + + // Add promo + await OST.promoField.fill(data.promo); + await expect(OST.checkoutLink).toHaveAttribute('href', new RegExp(`${data.promo}`)); + + // Change Forkflow step + await OST.workflowMenu.click(); + await page.locator(`div[data-key="${data.workflowStep_2}"]`).waitFor({ state: 'visible', timeout: 10000 }); + await page.locator(`div[data-key="${data.workflowStep_2}"]`).click(); + await expect(OST.checkoutLink).toHaveAttribute('href', new RegExp(`${data.workflowStep_2}`)); + }); + }); + + // Verify OST enebalement for old price in the promo price + test(`${features[9].name},${features[9].tags}`, async ({ page, baseURL }) => { + console.info(`[Test Page]: ${baseURL}${features[9].path}`); + + const testPage = `${baseURL}${features[9].path}${features[9].browserParams}${authToken}`; + const { data } = features[9]; + + let clipboardText; + + await test.step('Open Offer Selector Tool', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Enter Offer ID in the search field', async () => { + await OST.searchField.fill(data.offerID); + }); + + await test.step('Click Next button in OST', async () => { + await OST.nextButton.waitFor({ state: 'visible', timeout: 10000 }); + await OST.nextButton.click(); + }); + + await test.step('Validate tax inclusivity enablement', async () => { + await OST.price.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceOptical.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceStrikethrough.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceUse.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceOpticalUse.waitFor({ state: 'visible', timeout: 10000 }); + await OST.priceStrikethroughUse.waitFor({ state: 'visible', timeout: 10000 }); + + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=price'); + expect(await clipboardText).not.toContain('old='); + + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=priceOptical'); + expect(await clipboardText).not.toContain('old='); + + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('type=priceStrikethrough'); + expect(await clipboardText).not.toContain('old='); + + // Check tax label checkbox + await OST.oldPrice.click(); + + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('old=true'); + + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('old=true'); + + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).toContain('old=true'); + + // Uncheck tax label checkbox + await OST.oldPrice.click(); + + await OST.priceUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('old='); + + await OST.priceOpticalUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('old='); + + await OST.priceStrikethroughUse.click(); + clipboardText = await page.evaluate('navigator.clipboard.readText()'); + expect(await clipboardText).not.toContain('old='); + }); + }); +}); diff --git a/nala/features/promotions/promotions.page.js b/nala/features/promotions/promotions.page.js new file mode 100644 index 0000000000..484c813e6f --- /dev/null +++ b/nala/features/promotions/promotions.page.js @@ -0,0 +1,25 @@ +export default class CommercePage { + constructor(page) { + this.page = page; + + this.marqueeDefault = page.locator('.marquee #promo-test-page'); + this.marqueeReplace = page.locator('.marquee #marquee-promo-replace'); + this.marqueeFragment = page.locator('.marquee #fragment-marquee'); + this.textBlock = page.locator('.text-block'); + this.textDefault = page.locator('.text #default-text'); + this.textReplace = page.locator('.text #promo-text-replace'); + this.textInsertAfterMarquee = page.locator('.text #marquee-promo-text-insert'); + this.textInsertBeforeText = page.locator('.text #text-promo-text-insert'); + this.textInsertFuture = page.locator('.text #future-promo-text-insert'); + this.textInsertBeforeCommon = page.locator('.text #common-promo'); + this.textInsertBeforeCommonDE = page.locator('.text #german-promo'); + this.textInsertBeforeCommonFR = page.locator('.text #french-promo'); + this.mepMenuOpen = page.locator('.mep-open'); + this.mepPreviewButton = page.locator('//a[contains(text(),"Preview")]'); + this.mepManifestList = page.locator('.mep-manifest-list'); + this.mepInsertDefault = page.locator('//input[contains(@name,"promo-insert") and @value="default"]'); + this.mepInsertAll = page.locator('//input[contains(@name,"promo-insert") and @value="all"]'); + this.mepReplaceDefault = page.locator('//input[contains(@name,"promo-replace") and @value="default"]'); + this.mepReplaceAll = page.locator('//input[contains(@name,"promo-replace") and @value="all"]'); + } +} diff --git a/nala/features/promotions/promotions.spec.js b/nala/features/promotions/promotions.spec.js new file mode 100644 index 0000000000..44a30af081 --- /dev/null +++ b/nala/features/promotions/promotions.spec.js @@ -0,0 +1,163 @@ +module.exports = { + name: 'Promotions', + features: [ + { + tcid: '0', + name: '@Promo-insert', + path: '/drafts/nala/features/promotions/promo-insert', + data: { + textMarquee: 'Promo test page', + textAfterMarquee: 'Marquee promo text insert', + textBeforeText: 'Text promo text insert', + textDefault: 'Default text', + }, + tags: '@promo @commerce @regression', + }, + { + tcid: '1', + name: '@Promo-replace', + path: '/drafts/nala/features/promotions/promo-replace', + data: { + textReplaceMarquee: 'Marquee promo replace', + textReplace: 'Promo text replace', + }, + tags: '@promo @commerce @regression', + }, + { + tcid: '2', + name: '@Promo-remove', + path: '/drafts/nala/features/promotions/promo-remove', + data: { textMarquee: 'Promo test page' }, + tags: '@promo @commerce @regression', + }, + { + tcid: '3', + name: '@Promo-two-manifests', + path: '/drafts/nala/features/promotions/promo-default', + data: { + textReplaceMarquee: 'Marquee promo replace', + textReplace: 'Promo text replace', + textAfterMarquee: 'Marquee promo text insert', + textBeforeText: 'Text promo text insert', + }, + tags: '@promo @commerce @smoke @regression', + }, + { + tcid: '4', + name: '@Promo-replace-fragment', + path: '/drafts/nala/features/promotions/promo-with-fragments', + data: { textReplaceMarquee: 'Marquee promo replace' }, + tags: '@promo @commerce @regression', + }, + { + tcid: '5', + name: '@Promo-future', + path: '/drafts/nala/features/promotions/promo-future', + data: { + mepPath: '/drafts/nala/features/promotions/manifests/promo-insert-future.json--all', + textMarquee: 'Promo test page', + textDefault: 'Default text', + textFuture: 'Future promo text insert', + status: 'Scheduled - inactive', + manifestFile: 'promo-insert-future.json', + }, + tags: '@promo @commerce @regression', + }, + { + tcid: '6', + name: '@Promo-with-personalization', + path: '/drafts/nala/features/promotions/promo-with-personalization', + data: { + textMarquee: 'Promo test page', + textAfterMarquee: 'Marquee promo text insert', + textBeforeText: 'Text promo text insert', + }, + tags: '@promo @commerce @smoke @regression', + }, + { + tcid: '7', + name: '@Promo-with-personalization-and-target', + path: '/drafts/nala/features/promotions/promo-with-personalization-and-target', + data: { + textMarquee: 'Promo test page', + textAfterMarquee: 'Marquee promo text insert', + textBeforeText: 'Text promo text insert', + }, + tags: '@promo @commerce @smoke @regression', + }, + { + tcid: '8', + name: '@Promo-preview', + path: '/drafts/nala/features/promotions/promo-default', + data: { + mepInsertOn: '/drafts/nala/features/promotions/manifests/promo-insert.json--all', + mepReplaceOn: '/drafts/nala/features/promotions/manifests/promo-replace.json--all', + mepInsertOff: '/drafts/nala/features/promotions/manifests/promo-insert.json--default', + mepReplaceOff: '/drafts/nala/features/promotions/manifests/promo-replace.json--default', + textMarquee: 'Promo test page', + textDefault: 'Default text', + textAfterMarquee: 'Marquee promo text insert', + textBeforeText: 'Text promo text insert', + textReplaceMarquee: 'Marquee promo replace', + textReplace: 'Promo text replace', + inactiveStatus: 'Scheduled - inactive', + manifestInsertFile: 'promo-insert.json', + manifestReplaceFile: 'promo-replace.json', + }, + tags: '@promo @commerce @smoke @regression', + }, + { + tcid: '9', + name: '@Promo-page-filter-insert', + path: '/drafts/nala/features/promotions/promo-page-filter-insert', + data: { + textMarquee: 'Promo test page', + textDefault: 'Default text', + textAfterMarquee: 'Marquee promo text insert', + }, + tags: '@promo @commerce @regression', + }, + { + tcid: '10', + name: '@Promo-page-filter-replace', + path: '/drafts/nala/features/promotions/promo-page-filter-replace', + data: { + textReplaceMarquee: 'Marquee promo replace', + textDefault: 'Default text', + }, + tags: '@promo @commerce @regression', + }, + { + tcid: '11', + name: '@Promo-page-filter-geo', + path: '/drafts/nala/features/promotions/promo-page-filter', + data: { + textMarquee: 'Promo test page', + textDefault: 'Default text', + textBeforeText: 'Common Promo', + CO_DE: '/de', + textBeforeTextDE: 'German Promo', + CO_FR: '/fr', + textBeforeTextFR: 'French Promo', + }, + tags: '@promo @commerce @smoke @regression', + }, + { + tcid: '12', + name: '@Promo-remove-fragment', + path: '/drafts/nala/features/promotions/promo-with-fragments-remove', + tags: '@promo @commerce @regression', + }, + { + tcid: '13', + name: '@Promo-fragment-insert', + path: '/drafts/nala/features/promotions/promo-with-fragments-insert', + data: { + textMarquee: 'Fragment marquee', + textBeforeMarquee: 'Text promo text insert', + textAfterMarquee: 'Marquee promo text insert', + }, + tags: '@promo @commerce @regression', + }, + ], +}; diff --git a/nala/features/promotions/promotions.test.js b/nala/features/promotions/promotions.test.js new file mode 100644 index 0000000000..1f551afb07 --- /dev/null +++ b/nala/features/promotions/promotions.test.js @@ -0,0 +1,535 @@ +/* eslint-disable import/no-extraneous-dependencies, max-len, no-console, no-plusplus */ +import { expect, test } from '@playwright/test'; +import { features } from './promotions.spec.js'; +import PromoPage from './promotions.page.js'; + +const miloLibs = process.env.MILO_LIBS || ''; + +let PROMO; +test.beforeEach(async ({ page, baseURL }) => { + PROMO = new PromoPage(page); + const skipOn = ['bacom', 'business']; + + skipOn.some((skip) => { + if (baseURL.includes(skip)) test.skip(true, `Skipping the promo tests for ${baseURL}`); + return null; + }); +}); + +test.describe('Promotions feature test suite', () => { + // @Promo-insert - Validate promo insert text after marquee and before text component + test(`${features[0].name},${features[0].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[0].path}${miloLibs}`; + const { data } = features[0]; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify default test page content', async () => { + await expect(await PROMO.marqueeDefault).toBeVisible(); + await expect(await PROMO.marqueeDefault).toContainText(data.textMarquee); + + await expect(await PROMO.textDefault).toBeVisible(); + await expect(await PROMO.textDefault).toContainText(data.textDefault); + }); + + await test.step('Validate content insert after marquee', async () => { + await expect(await PROMO.textInsertAfterMarquee).toBeVisible(); + await expect(await PROMO.textInsertAfterMarquee).toContainText(data.textAfterMarquee); + }); + + await test.step('Validate content insert before text component', async () => { + await expect(await PROMO.textInsertBeforeText).toBeVisible(); + await expect(await PROMO.textInsertBeforeText).toContainText(data.textBeforeText); + }); + }); + + // @Promo-replace - Validate promo replaces marquee and text component + test(`${features[1].name},${features[1].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[1].path}${miloLibs}`; + const { data } = features[1]; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify default test page content is not visible', async () => { + await expect(await PROMO.marqueeDefault).not.toBeVisible(); + await expect(await PROMO.textDefault).not.toBeVisible(); + }); + + await test.step('Validate marque replace', async () => { + await expect(await PROMO.marqueeReplace).toBeVisible(); + await expect(await PROMO.marqueeReplace).toContainText(data.textReplaceMarquee); + }); + + await test.step('Validate text component replace', async () => { + await expect(await PROMO.textReplace).toBeVisible(); + await expect(await PROMO.textReplace).toContainText(data.textReplace); + }); + }); + + // @Promo-remove - Validate promo removes text component + test(`${features[2].name},${features[2].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[2].path}${miloLibs}`; + const { data } = features[2]; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify only default test page marquee is visible', async () => { + await expect(await PROMO.marqueeDefault).toBeVisible(); + await expect(await PROMO.marqueeDefault).toContainText(data.textMarquee); + await expect(await PROMO.textDefault).not.toBeVisible(); + }); + + await test.step('Validate text component removed', async () => { + await expect(await PROMO.textBlock).not.toBeVisible(); + }); + }); + + // @Promo-two-manifests - Validate 2 active manifests on the page + test(`${features[3].name},${features[3].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[3].path}${miloLibs}`; + const { data } = features[3]; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify default test page content is not visible', async () => { + await expect(await PROMO.marqueeDefault).not.toBeVisible(); + await expect(await PROMO.textDefault).not.toBeVisible(); + }); + + await test.step('Validate marque replace', async () => { + await expect(await PROMO.marqueeReplace).toBeVisible(); + await expect(await PROMO.marqueeReplace).toContainText(data.textReplaceMarquee); + }); + + await test.step('Validate text component replace', async () => { + await expect(await PROMO.textReplace).toBeVisible(); + await expect(await PROMO.textReplace).toContainText(data.textReplace); + }); + + await test.step('Validate content insert after marquee', async () => { + await expect(await PROMO.textInsertAfterMarquee).toBeVisible(); + await expect(await PROMO.textInsertAfterMarquee).toContainText(data.textAfterMarquee); + }); + + await test.step('Validate content insert before text component', async () => { + await expect(await PROMO.textInsertBeforeText).toBeVisible(); + await expect(await PROMO.textInsertBeforeText).toContainText(data.textBeforeText); + }); + }); + + // @Promo-replace-fragment - Validate fragment marquee replace + test(`${features[4].name},${features[4].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[4].path}${miloLibs}`; + const { data } = features[4]; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify default test page content is not visible', async () => { + await expect(await PROMO.marqueeFragment).not.toBeVisible(); + }); + + await test.step('Validate marque promo replace', async () => { + await expect(await PROMO.marqueeReplace).toBeVisible(); + await expect(await PROMO.marqueeReplace).toContainText(data.textReplaceMarquee); + }); + }); + + // @Promo-future - Validate active promo scheduled in the future + test(`${features[5].name},${features[5].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[5].path}${miloLibs}`; + const { data } = features[5]; + const previewPage = `${baseURL}${features[5].path}${'?mep='}${data.mepPath}&${miloLibs}`; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Validate manifest is on served on the page but inactive', async () => { + await PROMO.mepMenuOpen.click(); + await expect(await PROMO.mepManifestList).toBeVisible(); + await expect(await PROMO.mepManifestList).toContainText(data.status); + await expect(await PROMO.mepManifestList).toContainText(data.manifestFile); + }); + + await test.step('Verify default test page content', async () => { + await expect(await PROMO.marqueeDefault).toBeVisible(); + await expect(await PROMO.marqueeDefault).toContainText(data.textMarquee); + + await expect(await PROMO.textDefault).toBeVisible(); + await expect(await PROMO.textDefault).toContainText(data.textDefault); + }); + + await test.step('Validate no future insert on the page', async () => { + await expect(await PROMO.textInsertFuture).not.toBeVisible(); + }); + + await test.step('Navigate to the page with applied future promo and validate content', async () => { + await page.goto(previewPage); + await page.waitForLoadState('domcontentloaded'); + await expect(page).toHaveURL(previewPage); + console.info(`[Promo preview Page]: ${previewPage}`); + + await expect(await PROMO.marqueeDefault).toBeVisible(); + await expect(await PROMO.marqueeDefault).toContainText(data.textMarquee); + + await expect(await PROMO.textDefault).toBeVisible(); + await expect(await PROMO.textDefault).toContainText(data.textDefault); + + await expect(await PROMO.textInsertFuture).toBeVisible(); + await expect(await PROMO.textInsertFuture).toContainText(data.textFuture); + }); + }); + + // @Promo-with-personalization - Validate promo together with personalization and target OFF + test(`${features[6].name},${features[6].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[6].path}${miloLibs}`; + const { data } = features[6]; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify only default test page marquee is visible', async () => { + await expect(await PROMO.marqueeDefault).toBeVisible(); + await expect(await PROMO.marqueeDefault).toContainText(data.textMarquee); + await expect(await PROMO.textDefault).not.toBeVisible(); + }); + + await test.step('Validate content insert after marquee', async () => { + await expect(await PROMO.textInsertAfterMarquee).toBeVisible(); + await expect(await PROMO.textInsertAfterMarquee).toContainText(data.textAfterMarquee); + }); + + await test.step('Validate content insert before text component', async () => { + await expect(await PROMO.textInsertBeforeText).toBeVisible(); + await expect(await PROMO.textInsertBeforeText).toContainText(data.textBeforeText); + }); + }); + + // @Promo-with-personalization-and-target - Validate promo together with personalization and target ON + test(`${features[7].name},${features[7].tags}`, async ({ page, baseURL, browserName }) => { + test.skip(browserName === 'chromium', 'Skipping test for Chromium browser'); + + const testPage = `${baseURL}${features[7].path}${miloLibs}`; + const { data } = features[7]; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify only default test page marquee is visible', async () => { + await expect(await PROMO.marqueeDefault).toBeVisible(); + await expect(await PROMO.marqueeDefault).toContainText(data.textMarquee); + await expect(await PROMO.textDefault).not.toBeVisible(); + }); + + await test.step('Validate content insert after marquee', async () => { + await expect(await PROMO.textInsertAfterMarquee).toBeVisible(); + await expect(await PROMO.textInsertAfterMarquee).toContainText(data.textAfterMarquee); + }); + + await test.step('Validate content insert before text component', async () => { + await expect(await PROMO.textInsertBeforeText).toBeVisible(); + await expect(await PROMO.textInsertBeforeText).toContainText(data.textBeforeText); + }); + }); + + // @Promo-preview - Validate preview functionality + test(`${features[8].name},${features[8].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[8].path}${miloLibs}`; + const { data } = features[8]; + let previewPage; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Validate all manifests are served and active on the page', async () => { + await PROMO.mepMenuOpen.click(); + await expect(await PROMO.mepManifestList).toBeVisible(); + await expect(await PROMO.mepManifestList).not.toContainText(data.inactiveStatus); + await expect(await PROMO.mepManifestList).toContainText(data.manifestInsertFile); + await expect(await PROMO.mepManifestList).toContainText(data.manifestReplaceFile); + }); + + await test.step('Verify promo page content', async () => { + await expect(await PROMO.marqueeDefault).not.toBeVisible(); + await expect(await PROMO.textDefault).not.toBeVisible(); + + await expect(await PROMO.marqueeReplace).toBeVisible(); + await expect(await PROMO.marqueeReplace).toContainText(data.textReplaceMarquee); + + await expect(await PROMO.textReplace).toBeVisible(); + await expect(await PROMO.textReplace).toContainText(data.textReplace); + + await expect(await PROMO.textInsertAfterMarquee).toBeVisible(); + await expect(await PROMO.textInsertAfterMarquee).toContainText(data.textAfterMarquee); + + await expect(await PROMO.textInsertBeforeText).toBeVisible(); + await expect(await PROMO.textInsertBeforeText).toContainText(data.textBeforeText); + }); + + await test.step('Disable insert manifest and preview', async () => { + await PROMO.mepInsertDefault.click(); + await PROMO.mepPreviewButton.click(); + + await page.waitForLoadState('domcontentloaded'); + previewPage = decodeURIComponent(page.url()); + console.info(`[Preview Page]: ${previewPage}`); + expect(previewPage).toContain(data.mepInsertOff); + expect(previewPage).toContain(data.mepReplaceOn); + + await expect(await PROMO.marqueeDefault).not.toBeVisible(); + await expect(await PROMO.textDefault).not.toBeVisible(); + + await expect(await PROMO.textInsertAfterMarquee).not.toBeVisible(); + await expect(await PROMO.textInsertBeforeText).not.toBeVisible(); + + await expect(await PROMO.marqueeReplace).toBeVisible(); + await expect(await PROMO.marqueeReplace).toContainText(data.textReplaceMarquee); + + await expect(await PROMO.textReplace).toBeVisible(); + await expect(await PROMO.textReplace).toContainText(data.textReplace); + }); + + await test.step('Enable insert and disable replace manifest and preview', async () => { + await PROMO.mepMenuOpen.click(); + await PROMO.mepInsertAll.click(); + await PROMO.mepReplaceDefault.click(); + await PROMO.mepPreviewButton.click(); + + await page.waitForLoadState('domcontentloaded'); + previewPage = decodeURIComponent(page.url()); + console.info(`[Preview Page]: ${previewPage}`); + expect(previewPage).toContain(data.mepInsertOn); + expect(previewPage).toContain(data.mepReplaceOff); + + await expect(await PROMO.marqueeDefault).toBeVisible(); + await expect(await PROMO.marqueeDefault).toContainText(data.textMarquee); + await expect(await PROMO.textDefault).toBeVisible(); + await expect(await PROMO.textDefault).toContainText(data.textDefault); + + await expect(await PROMO.textInsertAfterMarquee).toBeVisible(); + await expect(await PROMO.textInsertAfterMarquee).toContainText(data.textAfterMarquee); + + await expect(await PROMO.textInsertBeforeText).toBeVisible(); + await expect(await PROMO.textInsertBeforeText).toContainText(data.textBeforeText); + + await expect(await PROMO.marqueeReplace).not.toBeVisible(); + await expect(await PROMO.textReplace).not.toBeVisible(); + }); + + await test.step('Desable all manifests and preview', async () => { + await PROMO.mepMenuOpen.click(); + await PROMO.mepInsertDefault.click(); + await PROMO.mepReplaceDefault.click(); + await PROMO.mepPreviewButton.click(); + + await page.waitForLoadState('domcontentloaded'); + previewPage = decodeURIComponent(page.url()); + console.info(`[Preview Page]: ${previewPage}`); + expect(previewPage).toContain(data.mepInsertOff); + expect(previewPage).toContain(data.mepReplaceOff); + + await expect(await PROMO.marqueeDefault).toBeVisible(); + await expect(await PROMO.marqueeDefault).toContainText(data.textMarquee); + await expect(await PROMO.textDefault).toBeVisible(); + await expect(await PROMO.textDefault).toContainText(data.textDefault); + + await expect(await PROMO.textInsertAfterMarquee).not.toBeVisible(); + await expect(await PROMO.textInsertBeforeText).not.toBeVisible(); + await expect(await PROMO.marqueeReplace).not.toBeVisible(); + await expect(await PROMO.textReplace).not.toBeVisible(); + }); + }); + + // @Promo-page-filter-insert - Validate promo page filter with insert action + test(`${features[9].name},${features[9].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[9].path}${miloLibs}`; + const { data } = features[9]; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify default test page content', async () => { + await expect(await PROMO.marqueeDefault).toBeVisible(); + await expect(await PROMO.marqueeDefault).toContainText(data.textMarquee); + + await expect(await PROMO.textDefault).toBeVisible(); + await expect(await PROMO.textDefault).toContainText(data.textDefault); + }); + + await test.step('Validate content insert after marquee', async () => { + await expect(await PROMO.textInsertAfterMarquee).toBeVisible(); + await expect(await PROMO.textInsertAfterMarquee).toContainText(data.textAfterMarquee); + }); + + await test.step('Validate other promo filter actions are not applied', async () => { + await expect(await PROMO.textInsertBeforeCommon).not.toBeVisible(); + await expect(await PROMO.marqueeReplace).not.toBeVisible(); + }); + }); + + // @Promo-page-filter-replace - Validate promo page filter with replace action + test(`${features[10].name},${features[10].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[10].path}${miloLibs}`; + const { data } = features[10]; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify default content', async () => { + await expect(await PROMO.marqueeDefault).not.toBeVisible(); + await expect(await PROMO.textDefault).toBeVisible(); + await expect(await PROMO.textDefault).toContainText(data.textDefault); + }); + + await test.step('Validate marque replace', async () => { + await expect(await PROMO.marqueeReplace).toBeVisible(); + await expect(await PROMO.marqueeReplace).toContainText(data.textReplaceMarquee); + }); + + await test.step('Validate other promo filter actions are not applied', async () => { + await expect(await PROMO.textInsertBeforeCommon).not.toBeVisible(); + await expect(await PROMO.textInsertAfterMarquee).not.toBeVisible(); + }); + }); + + // @Promo-page-filter-geo - Validate promo page filter in default, de and fr locales + test(`${features[11].name},${features[11].tags}`, async ({ page, baseURL }) => { + let testPage = `${baseURL}${features[11].path}${miloLibs}`; + const { data } = features[11]; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify default test page content', async () => { + await expect(await PROMO.marqueeDefault).toBeVisible(); + await expect(await PROMO.marqueeDefault).toContainText(data.textMarquee); + + await expect(await PROMO.textDefault).toBeVisible(); + await expect(await PROMO.textDefault).toContainText(data.textDefault); + }); + + await test.step('Validate content insert before text', async () => { + await expect(await PROMO.textInsertBeforeCommon).toBeVisible(); + await expect(await PROMO.textInsertBeforeCommon).toContainText(data.textBeforeText); + }); + + await test.step('Validate other promo filter actions are not applied', async () => { + await expect(await PROMO.textInsertAfterMarquee).not.toBeVisible(); + await expect(await PROMO.marqueeReplace).not.toBeVisible(); + }); + + await test.step('Go to the test page in DE locale', async () => { + testPage = `${baseURL}${data.CO_DE}${features[11].path}${miloLibs}`; + console.info('[Test Page][DE]: ', testPage); + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify page filter on DE page', async () => { + await expect(await PROMO.marqueeDefault).toBeVisible(); + await expect(await PROMO.textDefault).toBeVisible(); + await expect(await PROMO.textInsertBeforeCommonDE).toBeVisible(); + await expect(await PROMO.textInsertBeforeCommonDE).toContainText(data.textBeforeTextDE); + await expect(await PROMO.textInsertAfterMarquee).not.toBeVisible(); + await expect(await PROMO.marqueeReplace).not.toBeVisible(); + }); + + await test.step('Go to the test page in FR locale', async () => { + testPage = `${baseURL}${data.CO_FR}${features[11].path}${miloLibs}`; + console.info('[Test Page][FR]: ', testPage); + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify page filter on FR page', async () => { + await expect(await PROMO.marqueeDefault).toBeVisible(); + await expect(await PROMO.textDefault).toBeVisible(); + await expect(await PROMO.textInsertBeforeCommonFR).toBeVisible(); + await expect(await PROMO.textInsertBeforeCommonFR).toContainText(data.textBeforeTextFR); + await expect(await PROMO.textInsertAfterMarquee).not.toBeVisible(); + await expect(await PROMO.marqueeReplace).not.toBeVisible(); + }); + }); + + // @Promo-remove-fragment - Validate fragment marquee remove + test(`${features[12].name},${features[12].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[12].path}${miloLibs}`; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify default test page content is not visible', async () => { + await expect(await PROMO.marqueeFragment).not.toBeVisible(); + }); + }); + + // @Promo-fragment-insert - Validate promo insert text after and before fragment + test(`${features[13].name},${features[13].tags}`, async ({ page, baseURL }) => { + const testPage = `${baseURL}${features[13].path}${miloLibs}`; + const { data } = features[13]; + console.info('[Test Page]: ', testPage); + + await test.step('Go to the test page', async () => { + await page.goto(testPage); + await page.waitForLoadState('domcontentloaded'); + }); + + await test.step('Verify default test page content', async () => { + await expect(await PROMO.marqueeFragment).toBeVisible(); + await expect(await PROMO.marqueeFragment).toContainText(data.textMarquee); + }); + + await test.step('Validate content insert after marquee', async () => { + await expect(await PROMO.textInsertAfterMarquee).toBeVisible(); + await expect(await PROMO.textInsertAfterMarquee).toContainText(data.textAfterMarquee); + }); + + await test.step('Validate content insert before text component', async () => { + await expect(await PROMO.textInsertBeforeText).toBeVisible(); + await expect(await PROMO.textInsertBeforeText).toContainText(data.textBeforeMarquee); + }); + }); +}); diff --git a/nala/libs/imslogin.js b/nala/libs/imslogin.js new file mode 100644 index 0000000000..7ccb781f77 --- /dev/null +++ b/nala/libs/imslogin.js @@ -0,0 +1,37 @@ +/* eslint-disable import/no-import-module-exports, import/no-extraneous-dependencies, max-len, no-console */ +import { expect } from '@playwright/test'; +import selectors from '../features/imslogin/imslogin.page.js'; + +async function clickSignin(page) { + const signinBtn = page.locator(selectors['@gnav-signin']); + await expect(signinBtn).toBeVisible(); + await signinBtn.click(); +} + +async function fillOutSignInForm(props, page) { + expect(process.env.IMS_EMAIL, 'ERROR: No environment variable for email provided for IMS Test.').toBeTruthy(); + expect(process.env.IMS_PASS, 'ERROR: No environment variable for password provided for IMS Test.').toBeTruthy(); + + await expect(page).toHaveTitle(/Adobe ID/); + let heading = await page.locator(selectors['@page-heading']).first().innerText(); + expect(heading).toBe('Sign in'); + + // Fill out Sign-in Form + await expect(async () => { + await page.locator(selectors['@email']).fill(process.env.IMS_EMAIL); + await page.locator(selectors['@email-continue-btn']).click(); + await expect(page.locator(selectors['@password-reset'])).toBeVisible({ timeout: 45000 }); // Timeout accounting for how long IMS Login page takes to switch form + }).toPass({ + intervals: [1_000], + timeout: 10_000, + }); + + heading = await page.locator(selectors['@page-heading'], { hasText: 'Enter your password' }).first().innerText(); + expect(heading).toBe('Enter your password'); + await page.locator(selectors['@password']).fill(process.env.IMS_PASS); + await page.locator(selectors['@password-continue-btn']).click(); + await page.waitForURL(`${props.url}#`); + await expect(page).toHaveURL(`${props.url}#`); +} + +module.exports = { clickSignin, fillOutSignInForm }; diff --git a/nalarun.sh b/nalarun.sh new file mode 100755 index 0000000000..c9dcf40f5d --- /dev/null +++ b/nalarun.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +TAGS="" +REPORTER="" +EXCLUDE_TAGS="--grep-invert nopr" +EXIT_STATUS=0 +PR_NUMBER=$(echo "$GITHUB_REF" | awk -F'/' '{print $3}') +echo "PR Number: $PR_NUMBER" + +# Extract feature branch name from GITHUB_HEAD_REF +FEATURE_BRANCH="$GITHUB_HEAD_REF" +# Replace "/" characters in the feature branch name with "-" +FEATURE_BRANCH=$(echo "$FEATURE_BRANCH" | sed 's/\//-/g') +echo "Feature Branch Name: $FEATURE_BRANCH" + +PR_BRANCH_LIVE_URL_GH="https://$FEATURE_BRANCH--$prRepo--$prOrg.hlx.live" +# set pr branch url as env +export PR_BRANCH_LIVE_URL_GH +export PR_NUMBER + +echo "PR Branch live URL: $PR_BRANCH_LIVE_URL_GH" +echo "*******************************" + +# Convert GitHub Tag(@) labels that can be grepped +for label in ${labels}; do + if [[ "$label" = \@* ]]; then + label="${label:1}" + TAGS+="|$label" + fi +done + +# Remove the first pipe from tags if tags are not empty +[[ ! -z "$TAGS" ]] && TAGS="${TAGS:1}" && TAGS="-g $TAGS" + +# Retrieve GitHub reporter parameter if not empty +# Otherwise, use reporter settings in playwright.config.js +REPORTER=$reporter +[[ ! -z "$REPORTER" ]] && REPORTER="--reporter $REPORTER" + +echo "*** Running Nala on $FEATURE_BRANCH ***" +echo "Tags : $TAGS" +echo "npx playwright test ${TAGS} ${EXCLUDE_TAGS} ${REPORTER}" + +# Navigate to the GitHub Action path and install dependencies +cd "$GITHUB_ACTION_PATH" || exit +npm ci +npx playwright install --with-deps + +# Run Playwright tests on the specific projects using root-level playwright.config.js +# This will be changed later +echo "*** Running tests on specific projects ***" +npx playwright test --config=./playwright.config.js ${TAGS} ${EXCLUDE_TAGS} --project=milo-live-chromium ${REPORTER} || EXIT_STATUS=$? +npx playwright test --config=./playwright.config.js ${TAGS} ${EXCLUDE_TAGS} --project=milo-live-firefox ${REPORTER} || EXIT_STATUS=$? + +# Check if tests passed or failed +if [ $EXIT_STATUS -ne 0 ]; then + echo "Some tests failed. Exiting with error." + exit $EXIT_STATUS +else + echo "All tests passed successfully." +fi