Per playwright documentation you can mock an API call
This works fine for me but I have two tests that have different mock responses in the same file.
submitMock = (response) => page.route(/submit/, (route) =>
route.fulfill({
status: status || 200,
body: JSON.stringify(response),
}),
);
/// my test code
describe('fill form', () => {
beforeEach(async () => {
jest.resetAllMocks(); // doesn't clear mock requests
await setupAppMocks(); // some basic mocks
await page.goto(
`${localhost}/?foo=bar`,
);
});
afterAll(async () => {
await browser.close();
});
it('should test scenario 1', async () => {
expect.hasAssertions();
const responseMock = { test: 'test' };
await submitMock(responseMock); // first mock created here
await fillForm();
await clickSubmit();
await page.waitForSelector('[class*="CongratulationsText"]');
const sectionText = await page.$eval(
'[class*="CongratulationsText"]',
(e) => e.textContent,
);
expect(sectionText).toBe(
`Congratulations, expecting message for scenario 1`,
);
});
it('should test scenario 2', async () => {
expect.hasAssertions();
const responseMock = { test: 'test 2' };
await submitMock(responseMock); // second mock created here
await fillForm();
await clickSubmit();
await page.waitForSelector('[class*="CongratulationsText"]');
const sectionText = await page.$eval(
'[class*="CongratulationsText"]',
(e) => e.textContent,
);
// fails because first mock is still active
expect(sectionText).toBe(
`Congratulations, expecting message for scenario 2`,
);
});
});
Is there a way to clear the previous mock?
What I've tried so far is basically adding another mock but it doesn't seem to override the previous one. I'm assuming that the issue is that the mocks for the same pattern can't be overridden, but I don't have an easy way to fix the issue.
The way to unmock an API call is to use page.unroute Documentation here
page.unroute(/submit/);
Related
I want to be able to use a locator variable within all the tests without having to define it every time inside each test.
Something like:
// #ts-check
const { test, expect } = require('#playwright/test');
test.beforeEach( async ({ page }) => {
await page.goto('[desired URL]');
});
// I want to make this variable global to be able to use it within all the tests.
const signInBtn = page.getByTestId('some-button'); // how to resolve 'page' here??
test.describe('My set of tests', () => {
test('My test 1', async ({ page }) => {
await expect(page).toHaveTitle(/Some-Title/);
await expect(signInBtn).toBeEnabled(); // I wanna use the variable here...
});
test('My test 2', async ({ page }) => {
await signInBtn.click(); // ...and here, without having to define it every time inside each test.
});
});
PS: This snippet is just an example to pass the idea, not the actual project, pls don't be attached to it.
You don't have to .Use Page Object Model.. Keep the tests clean.
By using page object model we separate out locator definitions and test method definitions from the actual test to keep it simple, clean & reusable.
See below example:
//Page Object
// playwright-dev-page.js
const { expect } = require('#playwright/test');
exports.PlaywrightDevPage = class PlaywrightDevPage {
/**
* #param {import('#playwright/test').Page} page
*/
constructor(page) {
this.page = page;
this.getStartedLink = page.locator('a', { hasText: 'Get started' });
this.gettingStartedHeader = page.locator('h1', { hasText: 'Installation' });
this.pomLink = page.locator('li', { hasText: 'Guides' }).locator('a', { hasText: 'Page Object Model' });
this.tocList = page.locator('article div.markdown ul > li > a');
}
async goto() {
await this.page.goto('https://playwright.dev');
}
async getStarted() {
await this.getStartedLink.first().click();
await expect(this.gettingStartedHeader).toBeVisible();
}
async pageObjectModel() {
await this.getStarted();
await this.pomLink.click();
}
}
Now we can use the PlaywrightDevPage class in our tests.
// example.spec.js
const { test, expect } = require('#playwright/test');
const { PlaywrightDevPage } = require('./playwright-dev-page');
test('getting started should contain table of contents', async ({ page }) => {
const playwrightDev = new PlaywrightDevPage(page);
await playwrightDev.goto();
await playwrightDev.getStarted();
await expect(playwrightDev.tocList).toHaveText([
`How to install Playwright`,
`What's Installed`,
`How to run the example test`,
`How to open the HTML test report`,
`Write tests using web first assertions, page fixtures and locators`,
`Run single test, multiple tests, headed mode`,
`Generate tests with Codegen`,
`See a trace of your tests`
]);
});
test('should show Page Object Model article', async ({ page }) => {
const playwrightDev = new PlaywrightDevPage(page);
await playwrightDev.goto();
await playwrightDev.pageObjectModel();
await expect(page.locator('article')).toContainText('Page Object Model is a common pattern');
});
You could move it all into a describe block. So something like this should work:
test.describe('My set of tests', () => {
let signInBtn:Locator;
test.beforeEach( async ({ page }) => {
await page.goto('[desired URL]');
signInBtn = page.getByTestId('some-button');
});
test('My test 1', async ({ page }) => {
await expect(page).toHaveTitle(/Some-Title/);
await expect(signInBtn).toBeEnabled();
});
test('My test 2', async ({ page }) => {
await signInBtn.click();
});
});
Recently I had to write some tests that mock API requests with jest (24.8.0).
So, I wrote something like this:
class MyApi {
public async getSomeData() {
// request backend
}
}
describe("Some test", () => {
const apiSpy = jest.spyOn(MyApi, 'getSomeData');
afterEach(() => jest.restoreAllMocks());
test('some test 1', async () => {
// arrange
apiSpy.mockResolved(null);
// act
const result = await someFunctionCallingMyApi();
// assert
});
test('some test 2', async () => {
// arrange
apiSpy.mockResolved(someMockObject);
// act
const result = await someFunctionCallingMyApi();
// assert
});
// ...and more
});
but it appears that some mockResolved are ignored and my function is calling real API instead of mocked one.
Later I changed this code to:
describe("Some test", () => {
let apiSpy: jest.SpyInstance;
beforeEach(() => {
apiSpy = jest.spyOn(MyApi, 'getSomeData');
});
afterEach(() => jest.restoreAllMocks());
test('some test 1', async () => {
// arrange
apiSpy.mockResolved(null);
// act
const result = await someFunctionCallingMyApi();
// assert
});
test('some test 2', async () => {
// arrange
apiSpy.mockResolved(someMockObject);
// act
const result = await someFunctionCallingMyApi();
// assert
});
// ...and more
});
and my tests seem to work, its properly call mocked function.
Can someone explain why the first code does not work?
How is the correct way to move a group of tests into a separate class or Jest+puppeteer module, which would be called in other tests?
For example, I have a describe with tests, and I want to use this describe in other describe.
login.spec.js
'use strict'
const testConst = require('./const')
const selectors = require('./selectors')
const action = require('./actions')
const br = require('./browser')
const Auth = require('./login')
let page
let browser
beforeAll(async () => {
// set up Puppeteer
browser = await br.set_up_Puppeteer(true)
console.log('browser.isConnected')
// Web page
console.log('browser ready')
page = await br.see_webPage(browser, testConst.browser.url_address)
console.log('page ready')
console.log('fill field email and click login btn')
console.log('logged')
}, testConst.browser.timeout)
afterAll(async () => {
await browser.close()
}, testConst.browser.timeout)
Auth.login(page)
login.js
'use strict'
const testConst = require('../const.js')
const selectors = require('../selectors')
const action = require('../actions')
class login{
static async login(page) {
describe('go to OrgTracker from launchpad', () => {
test('open card Org Tracker tool in launchpad', async () => {
await page.waitForSelector(selectors, {
visible: true
})
await page.click(selectors)
await page.waitForSelector(selectors, {
visible: true
})
}, testConst.browser.timeout)
})
}
}
module.exports=login
But this combo not working, after run test:login I see error TypeError: Cannot read property 'waitForSelector' of undefined
Your problem is the Auth.login(page) statement at the end of your code. The page variable isn't initialized yet at that point. Besides, you're already calling it inside your beforeAll which seems about correct, depending on what you're trying to achieve.
works like this
login.spec.js
'use strict'
const testConst = require('../const')
const selectors = require('../selectors')
const action = require('../actions')
const br = require('../browser')
const Auth = require('../login')
let page
let browser
const emails_correctData = testConst.users.correctData.emails
const password_correctData = testConst.users.correctData.passwords
beforeAll(async () => {
// set up Puppeteer
browser = await br.set_up_Puppeteer(true);
console.log('browser.isConnected');
// Web page
page = await br.see_webPage(browser, testConst.browser.url_address, 'user');
console.log('browser ready');
}, testConst.browser.timeout)
afterAll(async () => {
await browser.close();
}, testConst.browser.timeout)
describe('login on system', async() => {
test('login page as ' + emails_correctData.email1 + ' logged correctly', async () => {
console.log('fill field email and click login btn')
await Auth.login( page, emails_correctData.email1, password_correctData.password)
// full page screenshot
await action.make_screenshot(page, testConst.screenshot.path_afterLogin, '_after_login.jpg')
console.log('logged')
}, testConst.browser.timeout)
})
login.js
'use strict'
const testConst = require('../const')
const selectors = require('../selectors')
const action = require('../actions')
class Auth{
static async login( page, emails_correctData, password) {
await page.click(selectors.login.signIn).then(async()=>{
await expect(page.waitForSelector(selectors.login.email)).toBeTruthy()
})
console.log('click signIn')
await action.fillField(page, selectors.login.email, emails_correctData)
console.log('fillField email')
await action.fillField(page, selectors.login.password, password)
console.log('fillField password')
await page.click(selectors.login.btnLogin).then(async()=>{
console.log('click btn signIn')
const userName = await page.$eval(selectors.menu.start, e => e.innerHTML)
expect(userName).toBe(testConst.users.correctData.userName.userName1)
},testConst.browser.timeout)
}
}
module.exports=Auth
But that doesn't solve my problem. I wanted to specify whole groups of tests in the class and make them where I need it.
We are creating a node app, based on express, to read from a static local file and return the JSON within the file.
Here is our json-file.js with our route method:
const readFilePromise = require('fs-readfile-promise');
module.exports = {
readJsonFile: async (req, res) => {
try {
const filePath = 'somefile.json';
const file = await readFilePromise(filePath, 'utf8');
res.send(file);
} catch(e) {
res.status(500).json(e);
}
},
};
we use a third party module, fs-readfile-promise which basically turns node readFileSync into a promise.
But we struggle to mock implementation of this third party, to be able to produce two tests: one based on simulated read file (promise resolved) and one based on rejection.
Here is our test file:
const { readJsonFile } = require('../routes/json-file');
const readFilePromise = require('fs-readfile-promise');
jest.mock('fs-readfile-promise');
const resJson = jest.fn();
const resStatus = jest.fn();
const resSend = jest.fn();
const res = {
send: resSend,
status: resStatus,
json: resJson,
};
resJson.mockImplementation(() => res);
resStatus.mockImplementation(() => res);
resSend.mockImplementation(() => res);
describe('json routes', () => {
beforeEach(() => {
resStatus.mockClear();
resJson.mockClear();
resSend.mockClear();
});
describe('when there is an error reading file', () => {
beforeEach(() => {
readFilePromise.mockImplementation(() => Promise.reject('some error'));
});
it('should return given error', () => {
readJsonFile(null, res);
expect(readFilePromise).lastCalledWith('somefile.json', 'utf8'); // PASS
expect(resStatus).lastCalledWith(500); // FAIL : never called
expect(resSend).lastCalledWith({ "any": "value" }); // FAIL : never called
});
});
});
We tried to place readFilePromise.mockImplementation(() => Promise.reject('some error')); at the top, just after the jest.mock() without more success.
The third party code is basically something like:
module.exports = async function fsReadFilePromise(...args) {
return new Promise(....);
}
How can we mock and replace implementation of the module to return either a Promise.resolve() or Promise.reject() depending on our test setup to make our test case pass within res.send() or res.status() method?
The last 2 assertions never pass because the test doesn't wait for the promise in: const file = await readFilePromise(filePath, 'utf8'); to resolve or reject in this case, therefore res.send or res.status never get called.
To fix it, readJsonFile is async, you should await it in the test:
it('should return given error', async () => {
await readJsonFile(null, res);
...
})
How can we mock and replace implementation of the module to return
either a Promise.resolve() or Promise.reject() depending on our test
setup to make our test case pass within res.send() or res.status()
method
Exactly how you're doing it:
readFilePromise.mockImplementation(() => Promise.reject('some error'));
or
readFilePromise.mockImplementation(() => Promise.resolve('SomeFileContent!'));
After I have successfully implemented one of methods to fetch some data and run test
Code:
const fetchData = async (url) => {
const response = await axios.get(url);
const contentType = response.headers['content-type'];
if (typeof response.data === 'object') {
return JSON.stringify(response.data);
}
throw new Error('Content-Type is unrecognized');
};
module.exports = fetchData;
And test:
describe('fetchData', () => {
it('should return json string response data on successful request', async () => {
const responseData = await fetchData(url);
const expectedData = JSON.stringify({ key1: 'value1' });
assert.deepEqual(responseData, expectedData, 'Response data doesn\'t match');
});
However, I wanted to implement scheduling to my method. I implemented in by using node-scheduler npm module.
After my modification
scheduler.scheduleJob({ start: startTime, end: endtTime }, async () => {
const fetchData = async (url) => {
const response = await axios.get(url);
}
Tests are failing immadietly, furthermore I noticed that error log is going continuously, therefore I have to kill test.
Does anyone have an idea why adding simple scheduler makes my error not working? I am using:
Node v.8.11.4
chai-as-promised
nock