From c0bd7467bb690a123d43ff1b1fc93a83e03dfb47 Mon Sep 17 00:00:00 2001 From: Andrei Varabyeu Date: Wed, 26 Nov 2025 17:15:25 +0100 Subject: [PATCH 1/3] Create src/pages/StockShareCalculatorPage.ts --- src/pages/StockShareCalculatorPage.ts | 65 +++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 src/pages/StockShareCalculatorPage.ts diff --git a/src/pages/StockShareCalculatorPage.ts b/src/pages/StockShareCalculatorPage.ts new file mode 100644 index 0000000..598312b --- /dev/null +++ b/src/pages/StockShareCalculatorPage.ts @@ -0,0 +1,65 @@ +import { BasePage } from './BasePage'; +import { Page, Locator } from '@playwright/test'; + +export class StockShareCalculatorPage extends BasePage { + private sharesInput: Locator; + private dateInput: Locator; + private submitButton: Locator; + private resultsTable: Locator; + private emptyStateMessage: Locator; + private loadingStateMessage: Locator; + private errorStateMessage: Locator; + + constructor(page: Page) { + super(page); + this.sharesInput = page.locator('#shares-input'); + this.dateInput = page.locator('#date-input'); + this.submitButton = page.locator('#submit-button'); + this.resultsTable = page.locator('#results-table'); + this.emptyStateMessage = page.locator('#empty-state'); + this.loadingStateMessage = page.locator('#loading-state'); + this.errorStateMessage = page.locator('#error-state'); + } + + async navigateToCalculator(): Promise { + await this.navigateTo('/stock-share-calculator'); + } + + async fillSharesInput(shares: string): Promise { + await this.fill('#shares-input', shares); + } + + async fillDateInput(date: string): Promise { + await this.fill('#date-input', date); + } + + async submitForm(): Promise { + await this.click('#submit-button'); + } + + async getTableHeaders(): Promise { + return await this.resultsTable.locator('thead th').allTextContents(); + } + + async getTableRows(): Promise { + const rows = await this.resultsTable.locator('tbody tr'); + const rowData: string[][] = []; + for (const row of await rows.elementHandles()) { + const cells = await row.locator('td').allTextContents(); + rowData.push(cells); + } + return rowData; + } + + async getEmptyStateMessage(): Promise { + return await this.getText('#empty-state'); + } + + async getLoadingStateMessage(): Promise { + return await this.getText('#loading-state'); + } + + async getErrorStateMessage(): Promise { + return await this.getText('#error-state'); + } +} \ No newline at end of file From 44bef0ca6e08aa4e8de7d64dfde1f46b67eb3376 Mon Sep 17 00:00:00 2001 From: Andrei Varabyeu Date: Wed, 26 Nov 2025 17:15:35 +0100 Subject: [PATCH 2/3] Create features/results-table.feature --- features/results-table.feature | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 features/results-table.feature diff --git a/features/results-table.feature b/features/results-table.feature new file mode 100644 index 0000000..f5c9a45 --- /dev/null +++ b/features/results-table.feature @@ -0,0 +1,54 @@ +Feature: Results Table Render and Accessibility + Ensure the results table renders correctly with accessible markup, proper formatting, and responsiveness. + + Scenario: Verify table renders with specified columns + Given I navigate to the Stock/Share Calculator page + When I enter "100" into the Shares input field + And I enter "2023-06-15" into the Date input field + And I submit the form + Then the results table should render with the following columns: + | Investment Date | + | Original Shares | + | Original Value | + | Current Shares | + | Current Value | + | % Return | + | Split Adjustment| + | Current Price | + + Scenario: Validate table markup for accessibility + Given I navigate to the Stock/Share Calculator page + When I submit the form + Then the table should have proper accessibility markup + + Scenario: Verify currency, percentage, shares, and split adjustment formatting + Given I navigate to the Stock/Share Calculator page + When I submit the form + Then currency values should display with thousands separators and 2 decimals + And percentages should display with sign and 2 decimals + And shares should display up to 4 decimals + And split adjustment should display as ratios + + Scenario: Validate table responsiveness and sticky headers + Given I navigate to the Stock/Share Calculator page + When I resize the viewport to 360px + Then the table should support horizontal scrolling with sticky headers + And no content or header truncation should occur + + Scenario: Verify table updates dynamically without page reload + Given I navigate to the Stock/Share Calculator page + When I enter "200" into the Shares input field + And I enter "2023-07-01" into the Date input field + And I submit the form + Then the results table should update dynamically without page reload + + Scenario: Verify Empty, Loading, and Error states + Given I navigate to the Stock/Share Calculator page + When I have not submitted the form + Then the table should display an Empty state message + + When I simulate a Loading state + Then the table should display a Loading state message + + When I simulate an Error state + Then the table should display an Error state message \ No newline at end of file From 0e00ada9803ac8ffe6df2312d72484e49a4c1dd7 Mon Sep 17 00:00:00 2001 From: Andrei Varabyeu Date: Wed, 26 Nov 2025 17:15:50 +0100 Subject: [PATCH 3/3] Create src/steps/results-table-steps.ts --- src/steps/results-table-steps.ts | 126 +++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 src/steps/results-table-steps.ts diff --git a/src/steps/results-table-steps.ts b/src/steps/results-table-steps.ts new file mode 100644 index 0000000..bc269fa --- /dev/null +++ b/src/steps/results-table-steps.ts @@ -0,0 +1,126 @@ +import { Given, When, Then } from '@cucumber/cucumber'; +import { expect } from '@playwright/test'; +import { StockShareCalculatorPage } from '../pages/StockShareCalculatorPage'; +import { Page } from '@playwright/test'; + +let page: Page; +let stockShareCalculatorPage: StockShareCalculatorPage; + +Given('I navigate to the Stock/Share Calculator page', async () => { + stockShareCalculatorPage = new StockShareCalculatorPage(page); + await stockShareCalculatorPage.navigateToCalculator(); +}); + +When('I enter {string} into the Shares input field', async (shares: string) => { + await stockShareCalculatorPage.fillSharesInput(shares); +}); + +When('I enter {string} into the Date input field', async (date: string) => { + await stockShareCalculatorPage.fillDateInput(date); +}); + +When('I submit the form', async () => { + await stockShareCalculatorPage.submitForm(); +}); + +Then('the results table should render with the following columns:', async (dataTable) => { + const expectedColumns = dataTable.raw().flat(); + const actualColumns = await stockShareCalculatorPage.getTableHeaders(); + expect(actualColumns).toEqual(expectedColumns); +}); + +Then('the table should have proper accessibility markup', async () => { + const tableMarkup = await stockShareCalculatorPage.resultsTable.evaluate((table) => { + return { + caption: table.querySelector('caption') !== null, + thead: table.querySelector('thead') !== null, + tbody: table.querySelector('tbody') !== null, + thScope: Array.from(table.querySelectorAll('th')).every((th) => th.getAttribute('scope') === 'col'), + }; + }); + + expect(tableMarkup.caption).toBe(true); + expect(tableMarkup.thead).toBe(true); + expect(tableMarkup.tbody).toBe(true); + expect(tableMarkup.thScope).toBe(true); +}); + +Then('currency values should display with thousands separators and 2 decimals', async () => { + const rows = await stockShareCalculatorPage.getTableRows(); + rows.forEach((row) => { + const currencyValue = row[2]; // Assuming Original Value is the 3rd column + expect(currencyValue).toMatch(/^\$\d{1,3}(,\d{3})*\.\d{2}$/); + }); +}); + +Then('percentages should display with sign and 2 decimals', async () => { + const rows = await stockShareCalculatorPage.getTableRows(); + rows.forEach((row) => { + const percentageValue = row[5]; // Assuming % Return is the 6th column + expect(percentageValue).toMatch(/^[+-]\d+\.\d{2}%$/); + }); +}); + +Then('shares should display up to 4 decimals', async () => { + const rows = await stockShareCalculatorPage.getTableRows(); + rows.forEach((row) => { + const sharesValue = row[3]; // Assuming Current Shares is the 4th column + expect(sharesValue).toMatch(/^\d+\.\d{4}$/); + }); +}); + +Then('split adjustment should display as ratios', async () => { + const rows = await stockShareCalculatorPage.getTableRows(); + rows.forEach((row) => { + const splitAdjustment = row[6]; // Assuming Split Adjustment is the 7th column + expect(splitAdjustment).toMatch(/^\d+:\d+$/); + }); +}); + +When('I resize the viewport to {int}px', async (width: number) => { + await page.setViewportSize({ width, height: 800 }); +}); + +Then('the table should support horizontal scrolling with sticky headers', async () => { + const isScrollable = await stockShareCalculatorPage.resultsTable.evaluate((table) => { + return table.scrollWidth > table.clientWidth; + }); + expect(isScrollable).toBe(true); + + const stickyHeaders = await stockShareCalculatorPage.resultsTable.locator('thead').evaluate((thead) => { + const style = window.getComputedStyle(thead); + return style.position === 'sticky'; + }); + expect(stickyHeaders).toBe(true); +}); + +Then('no content or header truncation should occur', async () => { + const headers = await stockShareCalculatorPage.resultsTable.locator('thead th').allTextContents(); + headers.forEach((header) => { + expect(header.length).toBeGreaterThan(0); + }); +}); + +Then('the results table should update dynamically without page reload', async () => { + const initialRows = await stockShareCalculatorPage.getTableRows(); + await stockShareCalculatorPage.fillSharesInput('200'); + await stockShareCalculatorPage.fillDateInput('2023-07-01'); + await stockShareCalculatorPage.submitForm(); + const updatedRows = await stockShareCalculatorPage.getTableRows(); + expect(updatedRows).not.toEqual(initialRows); +}); + +Then('the table should display an Empty state message', async () => { + const emptyMessage = await stockShareCalculatorPage.getEmptyStateMessage(); + expect(emptyMessage).toBe('No data available'); +}); + +Then('the table should display a Loading state message', async () => { + const loadingMessage = await stockShareCalculatorPage.getLoadingStateMessage(); + expect(loadingMessage).toBe('Loading data...'); +}); + +Then('the table should display an Error state message', async () => { + const errorMessage = await stockShareCalculatorPage.getErrorStateMessage(); + expect(errorMessage).toBe('An error occurred while fetching data'); +}); \ No newline at end of file