I try to write E2E test with playwright but something is wrong.
When I was initialize the test, test passed but actually, block did not go inside the bracket.
It also passes the test when I enter the wrong selector.
The code is below:
import { _electron as electron } from 'playwright';
import { test, expect, ElectronApplication, Page, BrowserContext, Locator } from '#playwright/test';
test.describe('Add Connection', async() => {
let electronApp: ElectronApplication;
let firstWindow: Page;
let context: BrowserContext;
test.beforeAll(async() => {
electronApp = await electron.launch({ args: ['.']} );
const appPath = await electronApp.evaluate(async({ app }) => {
return app.getAppPath();
});
console.log(appPath);
});
test('Try Connection', () => {
electronApp.on('window', async(page) => {
await page.getByTestId('settings').click({delay: 1000});
await page.getByTestId('connection').click({delay: 1000});
});
});
test.afterAll(async() => {
await electronApp.close();
});
});
I haven't seen enough documentation about E2E testing on Electron. How can we write a test where we can go to different pages by clicking on the buttons?
I solved this problem. The inside of 'Try Connection' test doesn't work because electronApp.on() is a callback function.
To write ElectronJS & Playwright test I choose these steps:
I got a Page object (firstWindow) after that I clicked on the buttons and went to the place I wanted to test.
The final code:
import { _electron as electron } from 'playwright';
import { test, expect, ElectronApplication, Page } from '#playwright/test';
test.describe('Add Connection', async() => {
let electronApp: ElectronApplication;
let firstWindow: Page;
test.beforeAll(async() => {
electronApp = await electron.launch({ args: ['.']} );
firstWindow = await electronApp.firstWindow();
});
test('Try Connection', async() => {
await firstWindow.title();
await firstWindow.click('xpath=//*[#id="sidemenu-container"]/a[3]', {delay: 1500});
await firstWindow.click('xpath=//*[#id="***"]/app-settings/div/div[1]/button[1]', {delay: 1500});
await firstWindow.click('xpath=//*[#id="***"]', {delay: 1500});
await firstWindow.getByPlaceholder('***').fill('emir connection');
await firstWindow.locator('#***').selectOption({label: '***'});
await firstWindow.click('xpath=//*[#id="***"]', {delay: 2000});
// for the wait, (fake click)
await firstWindow.click('xpath=//*[#id="***"]', {delay: 7000});
});
test.afterAll(async() => {
await electronApp.close();
});
});
Related
I have created a small React app and I want to test it using Playwright component testing
I have 3 components: App -> ChildComponent -> ChildChildComponent
I want to render (mount) the ChildComponent directly, and make assertions on it, but when I do that, some ContextApi functions that are defined in the App in the normal flow, are now undefined as the App component is not part of the component test.
So i'v trying to render the ChildComponent together with a face ContextApi Provider and pass mocks of those undefined functions, and then I get an infinite render loop for some reason.
How can I go about this, as this use case is typical in react component test.
Here is the test with all my failed mocking attempts separated:
test.only("validate CharacterModal", async ({ page, mount }) => {
const data = ['some-mocked-irrelevant-data']
// const setCurrentCharacter = () => {};
// const setIsCharacterModalOpen = () => {};
// const setCurrentCharacterMocked = sinon.stub("setCurrentCharacter").callsFake(() => {});
// const setIsCharacterModalOpenMocked = sinon.stub("setCurrentCharacter").callsFake(() => {});
// const setCurrentCharacter = jest.fn();
// const setIsCharacterModalOpen = jest.fn();
// const setCurrentCharacter = (): void => {};
// const setIsCharacterModalOpen = (): void => {};
// const setIsCharacterModalOpen = (isCharacterModalOpen: boolean): void => {};
const AppContext = React.createContext<any>(null);
await page.route("**/users*", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify(data),
});
});
const component = await mount(
<AppContext.Provider value={{ setCurrentCharacterMocked, setIsCharacterModalOpenMocked }}>
<CharacterModal />
</AppContext.Provider>
);
expect(await component.getByRole("img").count()).toEqual(4);
});
The beforeMount hook can be used for this. I recently added docs about this: https://github.com/microsoft/playwright/pull/20593/files.
// playwright/index.jsx
import { beforeMount, afterMount } from '#playwright/experimental-ct-react/hooks';
// NOTE: It's probably better to use a real context
const AppContext = React.createContext(null);
beforeMount(async ({ App, hooksConfig }) => {
if (hooksConfig?.overrides) {
return (
<AppContext.Provider value={hooksConfig.overrides}>
<App />
</AppContext.Provider>
);
}
});
// src/CharacterModal.test.jsx
import { test, expect } from '#playwright/experimental-ct-react';
import { CharacterModal } from './CharacterModal';
test('configure context through hooks config', async ({ page, mount }) => {
const component = await mount(<CharacterModal />, {
hooksConfig: { overrides: 'this is given to the context' },
});
});
I am using foreach to run the test for multiple users.
I can access the variable from foreach inside of beforeAll, but does not work inside of test
the following line does not get the value as expected, i verified i have value for that column in the .csv
await page.locator('#searchByCode').fill(${user.Code}); // Does not Work , get undefined
import fs from 'fs';
import path from 'path';
import { test, expect, Page } from '#playwright/test';
import { parse } from 'csv-parse/sync'; //requires https://www.npmjs.com/package/csv-parse
const users = parse(fs.readFileSync(path.join(__dirname, '../users.csv')), {
columns: true,
skip_empty_lines: true
});
//Want test inside of describe to run in serial mode
test.describe.configure({ mode: 'serial' });
test.use({ viewport: { width: 600, height: 900 } }); //Affects all Tests
let page: Page;
users.forEach(user => {
console.log(user.Email);
test.describe(`Login for user ${user.Email}`, () => {
test.beforeAll(async ({ browser }) => {
// Create page once and sign in.
page = await browser.newPage();
await page.goto('https://XXXXXXX.com');
await page.fill("#logInEmail",`${user.Email}`); **//This one works**
//find password text(#loginInPassword) and enter password
await page.fill('#loginInPassword', `${user.Password}`); **//This one works**
await Promise.all([
page.waitForNavigation(/*{ url: 'https://getinline.net/' }*/),
page.locator('#loginSignIn').click()
]);
});
test.afterAll(async () => {
await page.close();
});
test('SearchBusinessByCode', async ({ browser }, testInfo) => {
await page.locator('#searchByCode').fill(`${user.Password}`); **// Does not Work , get undefined**
await page.locator('button', { hasText: 'Search' }).click();
});
});
}); //forEach
I want to send parallel POST requests in puppeteer. I have to change the payload with every request (URL remains the same).
I tried using puppeteer cluster, but how do I change payload with every request when I queue the same request?
Using normal puppeteer
(async() => {
const browser = await puppeteer.launch({
args: [
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-web-security",
],
executablePath: 'C:/Program Files/..',
headless: false,
});
for(const id of Ids) {
const page = await browser.newPage();
await page.setDefaultNavigationTimeout(60000);
await page.evaluateOnNewDocument(() => {
// Some code
})
await page.setRequestInterception(true);
// Request intercept handler... will be triggered with
// each page.goto() statement
page.on('request', interceptedRequest => {
// Here, is where you change the request method and
// add your post data
var data = {
'method': 'POST',
'postData': JSON.stringify({
....
"similarMaterialId": `${id}`,
}),
'headers': {
.....
},
};
// Request modified... finish sending!
interceptedRequest.continue(data);
});
const response = await page.goto('https://.../getProductInfo');
const responseBody = await response.json();
try {
let title = responseBody.description;
let price = responseBody.price;
fs.appendFile('temp.tsv', `${title}\t${price}\n`, function (err) {
if (err) throw err;
})
}
catch {
console.log(id)
}
await page.close();
}
console.log("Code ended!!")
await browser.close();
})();
I want to create many pages in parallel on a single browser.
I want to compose fixtures. The first fixture should always be available (think of it as base class). second fixture will vary in different test files (think of it as derived class)
I tried following code and it's working as I was expecting. Is this ok to follow this approach or any better option available?
//baseFixture.js
import { test as base} from '#playwright/test';
interface MyFixtures {
fixture1: string;
}
export const test = base.extend<MyFixtures>({
fixture1: "fixture-one"
}, );
//derivedFixture.js
import {test as test1} from 'baseFixture'
interface MyFixtures2 {
fixture2: string;
}
export const test = test1.extend<MyFixtures2>({
fixture2: "fixture-two"
}, );
//in test_file.js
import {test} from 'derivedFixture'
test('should allow me use composed fixture', async ({ page, fixture1, fixture2 }) => {
console.log(`from first fixture ${fixture1}`)
console.log(`from second fixture ${fixture2}`)
});
Seems to me that you are using fixtures like POMs and you are overengineering tests. If it works for you and depending on what you want, then use it. If my assumption is correct instead of passing fixtures to another fixture pass POMs and you can even perform steps here to get that page into certain state and here is example from playwright page:
// my-test.js
const base = require('#playwright/test');
const { TodoPage } = require('./todo-page');
const { SettingsPage } = require('./settings-page');
// Extend base test by providing "todoPage" and "settingsPage".
// This new "test" can be used in multiple test files, and each of them will get the fixtures.
exports.test = base.test.extend({
todoPage: async ({ page }, use) => {
// Set up the fixture.
const todoPage = new TodoPage(page);
await todoPage.goto();
await todoPage.addToDo('item1');
await todoPage.addToDo('item2');
// Use the fixture value in the test.
await use(todoPage);
// Clean up the fixture.
await todoPage.removeAll();
},
settingsPage: async ({ page }, use) => {
await use(new SettingsPage(page));
},
});
exports.expect = base.expect;
Then in your test simply pass {todoPage} or {settingsPage} or both:
const { test, expect } = require('./my-test');
test.beforeEach(async ({ settingsPage }) => {
await settingsPage.switchToDarkMode();
});
test('basic test', async ({ todoPage, page }) => {
await todoPage.addToDo('something nice');
await expect(page.locator('.todo-item')).toContainText(['something nice']);
});
Also you can chain your fixtures here and reuse them, for eg. you could pass todoPage to settingsPage fixture:
settingsPage: async ({ todoPage}, use) => {
await use(new SettingsPage(page));
},
This way everything in todoPage will be executed then settingsPage and this is what you pass to your test, and I guess this is what you were trying to achive.
My approach is to use the base fixture as a dependent fixture in a derivative fixture:
import { test as base } from "#playwright/test"
interface MyFixtures1 {
fixture1: string
}
export const testBase = base.extend<{}, MyFixtures1>({
fixture1: [
async ({}, use) => {
console.log("fixture1 setup once per worker")
use("one")
console.log("fixture1 teardown once per worker")
},
{ scope: "worker" }
]
})
interface MyFixtures2 {
fixture2: string
}
export const test = testBase.extend<MyFixtures2>({
fixture2: async ({ fixture1 }, use) => {
console.log("fixture2 setup for each test")
use(`two-${fixture1}`)
console.log("fixture2 teardown for each test")
},
})
test("should allow me use composed fixture", async ({ fixture1, fixture2 }) => {
console.log(`from first fixture ${fixture1}`)
console.log(`from second fixture ${fixture2}`)
})
test("should base", async ({ fixture1 }) => {
console.log(`from first fixture ${fixture1}`)
})
test("should derived", async ({ fixture2 }) => {
console.log(`from second fixture ${fixture2}`)
})
More info about how to use fixtures in docs
I want to test my Electron application but I feel this is harder than I expected!
Just a simple thing as using the open file dialog seems impossible from what I've seen when I looked around a while!
Is it at all possible, or can I mock this behavior somehow?
My application adds the selected files to a file list and shows some result in a grid. If I can't open files I wont get the grid and can't test if it behaves as expected.
How should I approach this issue if I can't use the file dialog?
This is my test setup:
import { Application } from "spectron";
import { expect } from "chai";
describe("Application", function() {
this.timeout(10000);
let app: Application;
let browser: any;
before(async () => {
app = new Application({
path: electronPath,
args: [appPath],
});
await app.start();
browser = app.client;
await browser.waitUntilWindowLoaded();
});
after(() => {
await app.stop();
});
it("Starts application", async () => {
const count = await browser.getWindowCount();
expect(count).to.equal(1);
});
it("should add files", async function() {
await browser.click("#block-container > div.button-row > div:nth-child(1) > button:nth-child(1)");
// ???
});
});
And this is the addFiles method:
public addFiles() {
const selectedFiles: string[] = this.electronService.remote.dialog.showOpenDialogSync({
title: "Add files",
properties: ["openFile", "multiSelections"]
});
...
}