Sixian Li, 2023/02
Unit tests are fast and easy to run, but they can’t provide a complete picture of the system's functionality.
Pros
Pros
Cons
Playwright
Straightforward. Just enter the credentials.
test('test', async ({ page }) => {
await page.goto('https://a830edad9050849tesliomsit.sharepoint.com/sites/KnowledgeH');
await page.getByPlaceholder('Email, phone, or Skype').fill('admin@a830edad9050849tesliomsit.onmicrosoft.com');
await page.getByPlaceholder('Email, phone, or Skype').press('Enter');
await page.getByPlaceholder('Password').press('Enter');
await page.getByPlaceholder('Password').fill(password);
await page.getByRole('button', { name: 'Sign in' }).click();
await page.getByRole('button', { name: 'Yes' }).click();
});
Cypress
No easy way. Need additional sign in script and library(node-sp-auth). Cross-origin tests are just supported in Cypress 12 released in Dec. 2022, 9 years after its first release.
Cypress is in-process web automation. It injects itself into the web page, and drives tests using Web API.
Playwright is out-of-process. It connects to the browser using Chrome Devtool Protocol.
Playwright
Parallel and headless by default, no config at all.
> All tests run in worker processes. These processes are OS processes, running independently, orchestrated by the test runner. All workers have identical environments and each starts its own browser.
Cypress
Need additional configuration. They have a long page about parallelization: https://docs.cypress.io/guides/guides/parallelization
Playwright
Chromium, Firefox, WebKit
Cypress
WebKit is still experimental. Cross-origin issue is not resolved yet.
Playwright
Playwright
Same as testing-library. Our UT knowledge is transferrable.
await expect(page.getByRole('heading', { name: 'Sign up' })).toBeVisible();
await page.getByRole('checkbox', { name: 'Subscribe' }).check();
await page.getByRole('button', { name: /submit/i }).click();
Playwright
Cypress
CSS selector by default. Need to install testing-libnrary separately.
Playwright
Same as testing-library. Our UT knowledge is transferrable.
cy.get('input').should('be.disabled')
cy.get('ul li:first').should('have.class', 'active')
playwright codegen https://a830edad9050849tesliomsit.sharepoint.com/sites/KnowledgeH
playwright codegen https://a830edad9050849tesliomsit.sharepoint.com/sites/KnowledgeH
async function addExternalLink(page: Page, topicPage: TopicPage, link: string, linkName: string) {
await topicPage.edit();
await page.getByRole('button', { name: 'Add a file or page for this topic' }).click();
await page.locator('button[role="presentation"]:has-text("From a link")').click();
await page.getByPlaceholder('https://').fill(link);
await page.getByRole('textbox', { name: 'Name *' }).fill(linkName);
await page.getByRole('button', { name: 'Add' }).click();
await topicPage.publish();
expect(page.getByRole('link', { name: linkName })).toHaveCount(1);
}
playwright codegen --save-storage=auth.json https://a830edad9050849tesliomsit.sharepoint.com/sites/KnowledgeH
playwright codegen --load-storage=auth.json https://a830edad9050849tesliomsit.sharepoint.com/sites/KnowledgeH
test.use({
storageState: 'auth.json'
});
playwright codegen --save-storage=auth.json https://a830edad9050849tesliomsit.sharepoint.com/sites/KnowledgeH
playwright codegen --load-storage=auth.json https://a830edad9050849tesliomsit.sharepoint.com/sites/KnowledgeH
test.use({
storageState: 'auth.json'
});
// global-setup.ts
import { chromium, FullConfig } from '@playwright/test';
async function globalSetup(config: FullConfig) {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://github.com/login');
await page.getByLabel('Username or email address').fill('username');
await page.getByLabel('Password').fill('password');
await page.getByRole('button', { name: 'Sign in' }).click();
// Save signed-in state to 'storageState.json'.
await page.context().storageState({ path: 'storageState.json' });
await browser.close();
}
export default globalSetup;
export class TopicPage {
readonly page: Page;
constructor(page: Page) {
this.page = page;
}
async create() {
...
}
async delete() {
await this.page.getByRole('menuitem', { name: 'Page details' }).click();
await this.page.getByRole('button', { name: 'Delete page' }).click();
await this.page.getByRole('button', { name: 'Delete' }).click();
await this.page.waitForURL(`${KNOWLEDGE_HUB_URL}/SitePages/Home.aspx`);
}
async edit() {
...
}
async publish() {
...
}
}
export class ManageTopicsPage {
readonly page: Page;
constructor(page: Page) {
this.page = page;
}
async goToRemovedView() {
await this.page.getByRole('button', { name: 'View removed topics' }).click();
await expect(this.page.getByRole('heading', { name: 'Removed topics you can manage' })).toBeVisible();
await expect(
this.page.getByRole('button', { name: 'Trend of topics by status over the past 30 days' })
).toBeHidden();
}
async returnToActiveView() {
...
}
async openFilterPane() {
...
}
}
export const test = base.extend<Fixtures>({
topicPage: async ({ page }, use) => {
const topicPage = new TopicPage(page);
await topicPage.create();
await use(topicPage);
await topicPage.delete();
},
manageTopicsPage: async ({ page }, use) => {
const manageTopicsPage = new ManageTopicsPage(page);
await manageTopicsPage.load();
await use(manageTopicsPage);
}
});
test('should allow adding external resource from link tab', async ({ page, topicPage }) => {
await topicPage.edit();
await page.getByRole('button', { name: 'Add a file or page for this topic' }).click();
await page.locator('button[role="presentation"]:has-text("From a link")').click();
await page.getByPlaceholder('https://').fill(link);
await page.getByRole('textbox', { name: 'Name *' }).fill(linkName);
await page.getByRole('button', { name: 'Add' }).click();
await topicPage.publish();
expect(page.getByRole('link', { name: linkName })).toHaveCount(1);
});
expect(await page.screenshot()).toMatchSnapshot();
await expect(page.getByRole('link', { name: newName })).toHaveCount(1);
await hideElements([page.getByRole('button', { name: /feedback/i })]);
expect(
await topicPage.featureTagLocator('ResourcesWebPart').screenshot({ animations: 'disabled' })
).toMatchSnapshot();
// Run all tests
playwright test
// Run test with specific title
playwright test -g "add a todo item"
// Headed mdoe
playwright test landing-page.spec.ts --headed
// Specific project
playwright test landing-page.ts --project=chromium
Hand written execution log
Find elements by automation id
Slow pipeline
Can't even import code from source file