-
Notifications
You must be signed in to change notification settings - Fork 321
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(testkit): add playwright test drivers (#2561)
- Loading branch information
Showing
42 changed files
with
18,465 additions
and
16,678 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,4 +12,6 @@ dist | |
coverage | ||
.scannerwork | ||
*.env | ||
.DS_Store | ||
.DS_Store | ||
packages/testkit/test-results | ||
packages/testkit/reports |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import { test, Page, Locator } from "@playwright/test"; | ||
|
||
/** | ||
* Class representing a base element for Playwright tests. | ||
*/ | ||
export class BaseElement { | ||
page : Page; | ||
locator : Locator; | ||
elementReportName: String | ||
/** | ||
* Create a BaseElement. | ||
* @param {Object} page - The Playwright page object. | ||
* @param {Object} locator - The locator for the element. | ||
* @param {string} elementReportName - The name for reporting purposes. | ||
*/ | ||
constructor(page: Page, locator: Locator, elementReportName: String) { | ||
this.page = page; | ||
this.locator = locator; | ||
this.elementReportName = elementReportName; | ||
} | ||
|
||
/** | ||
* Get the locator of the element. | ||
* @returns {Object} - The locator of the element. | ||
*/ | ||
getLocator() { | ||
return this.locator; | ||
} | ||
|
||
/** | ||
* Wait for the list elements to stabilize (i.e., the count of items remains constant for a specified duration). | ||
* @returns {Promise<void>} | ||
*/ | ||
async waitForElementsGroup(locator: Locator, elementReportName: String): Promise<void> { | ||
await test.step(`Wait for ${elementReportName} items to stabilize`, async () => { | ||
let previousCount = 0; | ||
let stableCountTime = 0; | ||
const stabilizationTimeMs = 500; | ||
while (true) { | ||
const currentCount = await this.locator.locator(locator).count(); | ||
|
||
if (currentCount === previousCount) { | ||
stableCountTime += 100; // Increase stable time by the check interval (100ms) | ||
} else { | ||
stableCountTime = 0; // Reset if the count changes | ||
} | ||
if (stableCountTime >= stabilizationTimeMs) { | ||
break; // Break the loop if count has been stable for the desired duration | ||
} | ||
previousCount = currentCount; | ||
await this.page.waitForTimeout(100); // Polling interval (100ms) | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Check if the element is enabled. | ||
* @returns {Promise<boolean>} - Returns true if the element is enabled, otherwise false. | ||
*/ | ||
async isEnabled(): Promise<boolean> { | ||
let isEnabled: boolean = false; | ||
await test.step(`Return if ${this.elementReportName} is enabled`, async () => { | ||
isEnabled = await this.locator.isEnabled(); | ||
return isEnabled; | ||
}); | ||
return isEnabled; | ||
} | ||
|
||
/** | ||
* Scroll the element into view if needed. | ||
* @returns {Promise<void>} | ||
*/ | ||
async scrollIntoView(): Promise<void> { | ||
await test.step(`Scroll ${this.elementReportName} into view`, async () => { | ||
await this.locator.scrollIntoViewIfNeeded(); | ||
}); | ||
} | ||
async getAttributeValue(attributeName: string, options: any = { timeout: 10000, pollInterval: 500 }) : Promise <string | null> { | ||
let attributeValue = null; | ||
|
||
await test.step(`Get attribute ${attributeName} of ${this.elementReportName}`, async () => { | ||
const startTime = Date.now(); | ||
|
||
while (Date.now() - startTime < options.timeout) { | ||
attributeValue = await this.locator.getAttribute(attributeName); | ||
|
||
if (attributeValue !== null) { | ||
break; | ||
} | ||
while (Date.now() - startTime < options.timeout) { | ||
await new Promise(resolve => setTimeout(resolve, options.pollInterval)); | ||
} | ||
if (attributeValue === null) { | ||
throw new Error(`Attribute ${attributeName} did not exist after ${options.timeout} ms`); | ||
} | ||
} | ||
}); | ||
return attributeValue; | ||
} | ||
|
||
async getText() : Promise<string|undefined> { | ||
let text: string|undefined; | ||
await test.step(`Get text of ${this.elementReportName}`, async () => { | ||
text = await this.locator.innerText(); | ||
return text; | ||
}); | ||
return text; | ||
} | ||
|
||
async waitFor(options = {}): Promise<void> { | ||
await test.step(`Wait for ${this.elementReportName}`, async () => { | ||
await this.locator.waitFor(options); | ||
}); | ||
} | ||
|
||
async waitForAbsence() : Promise<void>{ | ||
await test.step(`Wait for ${this.elementReportName} to be absent`, async () => { | ||
await this.waitFor({ state: "detached" }); | ||
}); | ||
} | ||
|
||
async count() : Promise<number>{ | ||
let count: number=0; | ||
await test.step(`Count elements matching ${this.elementReportName}`, async () => { | ||
count = await this.locator.count(); | ||
}); | ||
return count; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# `@vibe/testkit` | ||
|
||
> TODO: description | ||
## Usage | ||
|
||
``` | ||
const testingKit = require('@vibe/testkit'); | ||
// TODO: DEMONSTRATE API | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { test, expect } from '@playwright/test'; | ||
import { Button } from '../buttons/Button'; | ||
import { buttonStory } from './utils/url-helper'; | ||
|
||
test('should fire a click event and log to console', async ({ page }) => { | ||
// Navigate to the Storybook page with the component | ||
await page.goto(buttonStory, {timeout: 100000}); | ||
// Locate the iframe where the button is rendered | ||
const frame = page.frameLocator("[id='storybook-preview-iframe']"); | ||
const button = new Button(page, frame.locator('button[data-testid="button"]'), 'Button'); | ||
|
||
//TODO - find a better way to wait for the storybook to load | ||
while (await button.locator.isVisible() === false) { | ||
await page.waitForTimeout(30000); | ||
await page.reload(); | ||
if (await button.locator.isVisible() === true) { | ||
break; | ||
} | ||
} | ||
// Add a listener to capture console logs | ||
let consoleMessage = ''; | ||
page.on('console', async (msg) => { | ||
const values = await Promise.all(msg.args().map(arg => arg.jsonValue())); | ||
consoleMessage = values.join(' '); | ||
}); | ||
|
||
// Attach a click event listener that logs a message to the console | ||
await button.locator.evaluate((buttonElement) => { | ||
buttonElement.addEventListener('click', () => { | ||
console.log('Button clicked'); // Log to console when clicked | ||
}); | ||
}); | ||
// Click the button | ||
await button.click(); | ||
|
||
// Wait a bit to ensure the console log is captured | ||
await page.waitForTimeout(500); | ||
|
||
// Verify the console log contains the expected message | ||
expect(consoleMessage).toContain('Button clicked'); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import { test, expect } from "@playwright/test"; | ||
import { ButtonGroup } from "../buttons/ButtonGroup"; | ||
import { buttonGroupStory } from "./utils/url-helper"; | ||
|
||
|
||
test.describe("ButtonGroup Class with Storybook", () => { | ||
let buttonGroup; | ||
|
||
test.beforeEach(async ({ page }) => { | ||
// Navigate to the Storybook story where the ButtonGroup component is rendered | ||
await page.goto(buttonGroupStory); | ||
//TODO - find a better way to wait for the storybook to load | ||
// Locate the iframe where the Storybook component is rendered | ||
const frame = page.frameLocator("[id='storybook-preview-iframe']"); | ||
|
||
// Locate the button group inside the iframe | ||
const buttonGroupLocator = frame.locator('div[data-testid="button-group"]'); | ||
while (await buttonGroupLocator.isVisible() === false) { | ||
await page.waitForTimeout(30000); | ||
await page.reload(); | ||
if (await buttonGroupLocator.isVisible() === true) { | ||
break; | ||
} | ||
} | ||
// Initialize ButtonGroup with the Playwright page and locator | ||
buttonGroup = new ButtonGroup(page, buttonGroupLocator, "Test Button Group"); | ||
}); | ||
|
||
test("should initialize buttons if needed", async () => { | ||
// Initialize the buttons inside the ButtonGroup | ||
await buttonGroup.initializeButtonsIfNeeded(); | ||
|
||
// Verify that buttons are initialized | ||
expect(buttonGroup.buttonsInitialized).toBe(true); | ||
|
||
// Verify that buttons exist in the items array | ||
expect(buttonGroup.items.length).toBeGreaterThan(0); // Ensure that buttons were initialized | ||
}); | ||
|
||
test("should retrieve a button by name and click it", async ({ page }) => { | ||
// Initialize the buttons inside the ButtonGroup | ||
await buttonGroup.initializeButtonsIfNeeded(); | ||
|
||
const button = await buttonGroup.getButtonByName("Beta"); // Adjust the button name as needed | ||
|
||
// Add a listener for console logs to capture the click event | ||
let consoleMessage = ""; | ||
page.on("console", async msg => { | ||
const values = await Promise.all(msg.args().map(arg => arg.jsonValue())); | ||
consoleMessage = values.join(" "); | ||
}); | ||
|
||
// Attach a click listener that logs a message to the console when the button is clicked | ||
await button.locator.evaluate(buttonElement => { | ||
buttonElement.addEventListener("click", () => { | ||
console.log("Button clicked: Beta"); // Log to console when clicked | ||
}); | ||
}); | ||
|
||
// Click the button | ||
await buttonGroup.click("Beta"); | ||
|
||
// Wait a bit to ensure the console log is captured | ||
await page.waitForTimeout(500); | ||
|
||
// Verify the console message contains the expected log | ||
expect(consoleMessage).toContain("Button clicked: Beta"); | ||
}); | ||
|
||
test("should throw an error if trying to click a non-existent button", async () => { | ||
await expect(buttonGroup.click("NonExistentButton")).rejects.toThrow("Invalid button name provided"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { test, expect } from "@playwright/test"; | ||
import { Checkbox } from "../inputs/Checkbox"; | ||
import { checkboxStory } from "./utils/url-helper"; | ||
|
||
test.describe("menuButton Class with Storybook", () => { | ||
let checkbox; | ||
|
||
test.beforeEach(async ({ page }) => { | ||
await page.goto(checkboxStory); | ||
const frame = page.frameLocator("[id='storybook-preview-iframe']"); | ||
const checkboxLocator = frame.locator('[data-testid="checkbox-checkbox"]'); | ||
checkbox = new Checkbox(page, checkboxLocator, "Test checkbox button"); | ||
}); | ||
|
||
test("set checkbox", async ({page}) => { | ||
if (await checkbox.isChecked()){ | ||
await checkbox.setChecked(false); | ||
expect(await checkbox.isChecked()).toBe(false); | ||
}else { | ||
await checkbox.setChecked(true); | ||
expect(await checkbox.isChecked()).toBe(true); | ||
} | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { test, expect } from "@playwright/test"; | ||
import { Dropdown } from "../inputs/Dropdown"; | ||
import { dropdownStory } from "./utils/url-helper"; | ||
|
||
|
||
test.describe("dropdown Class with Storybook", () => { | ||
let DropDown; | ||
|
||
test.beforeEach(async ({ page }) => { | ||
await page.goto(dropdownStory); | ||
const frame = page.frameLocator("[id='storybook-preview-iframe']"); | ||
const DropDownLocator = frame.locator('[id="dropdown-menu-id"]'); | ||
DropDown = new Dropdown(page, DropDownLocator, "Test DropDown"); | ||
}); | ||
|
||
test("set dropdown value", async ({page}) => { | ||
await DropDown.selectItem("2"); | ||
await page.waitForTimeout(500); | ||
expect(await DropDown.getText()).toContain("Option 2"); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { test, expect } from "@playwright/test"; | ||
import { MenuButton } from "../buttons/MenuButton"; | ||
import { menuButtonStory } from "./utils/url-helper"; | ||
|
||
test.describe("menuButton Class with Storybook", () => { | ||
let menuButton; | ||
|
||
test.beforeEach(async ({ page }) => { | ||
await page.goto(menuButtonStory); | ||
const frame = page.frameLocator("[id='storybook-preview-iframe']"); | ||
const menubuttonLocator = frame.locator('[data-testid="menu-button"]'); | ||
menuButton = new MenuButton(page, menubuttonLocator, "Test menu button"); | ||
}); | ||
|
||
test("should open and close menu", async ({page}) => { | ||
await menuButton.openMenu(); | ||
await page.waitForTimeout(500); | ||
expect(await menuButton.isExpanded()).toBe(true); | ||
await menuButton.closeMenu(); | ||
await page.waitForTimeout(500); | ||
expect(await menuButton.isExpanded()).toBe(false); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { test, expect } from "@playwright/test"; | ||
import { TextArea } from "../inputs/TextArea"; | ||
import { textAreaStory } from "./utils/url-helper"; | ||
|
||
test.describe("textArea Class with Storybook", () => { | ||
let textArea; | ||
let textAreaLocator | ||
|
||
test.beforeEach(async ({ page }) => { | ||
await page.goto(textAreaStory); | ||
const frame = page.frameLocator("[id='storybook-preview-iframe']"); | ||
textAreaLocator = frame.locator('[data-testid="text-area"]'); | ||
textArea = new TextArea(page, textAreaLocator, "Test text field"); | ||
}); | ||
|
||
test("set text in textarea", async ({page}) => { | ||
await textArea.setText("Test Text"); | ||
expect(textArea.textAreaInput.locator).toHaveValue("Test Text"); | ||
}); | ||
}); |
Oops, something went wrong.