Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions features/results-table.feature
Original file line number Diff line number Diff line change
@@ -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
65 changes: 65 additions & 0 deletions src/pages/StockShareCalculatorPage.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
await this.navigateTo('/stock-share-calculator');
}

async fillSharesInput(shares: string): Promise<void> {
await this.fill('#shares-input', shares);
}

async fillDateInput(date: string): Promise<void> {
await this.fill('#date-input', date);
}

async submitForm(): Promise<void> {
await this.click('#submit-button');
}

async getTableHeaders(): Promise<string[]> {
return await this.resultsTable.locator('thead th').allTextContents();
}

async getTableRows(): Promise<string[][]> {
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<string> {
return await this.getText('#empty-state');
}

async getLoadingStateMessage(): Promise<string> {
return await this.getText('#loading-state');
}

async getErrorStateMessage(): Promise<string> {
return await this.getText('#error-state');
}
}
126 changes: 126 additions & 0 deletions src/steps/results-table-steps.ts
Original file line number Diff line number Diff line change
@@ -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');
});