This is my start with playwright and I try to test my React app with it.
It might be that there is a similar question somewhere here, however I've tried all the possible non-specific answers from StackOverflow and Github issues.
This is my test:
import {expect, test} from "#playwright/test";
test.describe('App general functionality', () => {
test('Theme switches normally', async ({page}) => {
const app = await page.getByTestId('app');
const themeSwitch = await page.getByTestId('themeSwitch');
const classList = await app.evaluate(node => {
console.log(node);
});
// const classList = await app.getAttribute('class');
});
});
I've tried installing extended expect types for toHaveClass and checked if app is present. In console it returns locator and elements inside the app. App is a test id on the root div of the application.
However the error is constant:
locator.evaluate: Target closed
=========================== logs ===========================
waiting for getByTestId('app')
============================================================
And it is one this line:
const classList = await app.evaluate // or app.getAttribute('class')
The app div:
<div data-test-id={'app'} className={`app ${appStore.isDarkTheme ? 'dark' : 'light'}`}>
Git url
I don't think the test id is set properly. It should be data-testid based on the following experiment:
import {expect, test} from "#playwright/test"; // ^1.30.0
const html = `<!DOCTYPE html>
<html><body><div data-testid="app" class="foo bar baz">hi</div></body></html>`;
test.describe("App general functionality", () => {
test("Theme switches normally", async ({page}) => {
await page.setContent(html); // for easy reproducibility
const el = await page.getByTestId("app");
const classList = await el.evaluate(el => [...el.classList]);
expect(classList).toEqual(["foo", "bar", "baz"]);
});
});
If you can't change the element's property, maybe try
const el = await page.$('[data-test-id="app"]');
Pehaps a bit more idiomatic (assuming you've fixed testid):
await expect(page.getByTestId("app")).toHaveClass(/\bfoo\b/);
await expect(page.getByTestId("app")).toHaveClass(/\bbar\b/);
await expect(page.getByTestId("app")).toHaveClass(/\bbaz\b/);
This handles extra classes and different ordering of the classes. See also Check if element class contains string using playwright.
I've followed a guide on how to add dynamic content to the cache and and show a error message through a fallback.json file (the error message is supposed to show up in same div where the dynamic content is).
The problem I am facing right now is that the website got multiple pages and I do only want to show the fallback text if I haven't loaded dynamic content on a specific site (the index site on this example). I am currently getting the fallback text on sites I haven't visited when I go offline.
So my question is:
How can the service worker act differently on different sites?
And can I for example use a JS method instead of using a fallback.json file?
Serviceworker.js:
const staticAssets = [
'./',
'./favicon.ico',
'./fallback.json'
];
// Call Install Event
self.addEventListener('install', async e => {
console.log("From SW: Install Event");
const cache = await caches.open('staticCache');
cache.addAll(staticAssets);
});
// Call Fetch Event
self.addEventListener('fetch', async event => {
console.log('Service Worker: Fetching');
const req = event.request;
const url = new URL(req.url);
event.respondWith(networkFirst(req));
});
// Network first for latest articles - if not available get cached
async function networkFirst(req) {
const cache = await caches.open('dynamicContent');
try {
// Try go through the network
const res = await fetch(req,);
// Store to cache
cache.put(req, res.clone());
return res;
} catch (e) {
// IF offline - return from cache
const cachedResponse = await cache.match(req);
// Return cachedresponce first, if no cache found - return fallback.json
return cachedResponse || await caches.match('./fallback.json');
}
}
And my manifest.json file:
{
"articles": [
{
"title": "No articles found!!",
"url": "",
"urlToImage": "",
"description": "Try reload your page once again when you are online."
}
]
}
I managed to solve this by using a IndexedDB. My mistake was trying to save dynamic content in the cache. The better solution for me was to handle and load data in a IndexedDB.
Reference: https://developers.google.com/web/ilt/pwa/working-with-indexeddb
I'm using Jest Puppeteer and I have a situation where I'd like to run my login test (which sets cookie/localStorage for the authentication) first and run the others after, however, I know that Jest doesn't work this way - as it searches the local filesystem and runs tests based on the patterns in the filename, and the order in which they run is different.
I'm not entirely sure I'm going about this the correct way as I'm relying on a test to set the the authentication session for the other tests.
Is it possible to do the above, or do I need to rethink my approach?
This is not a timely answer. I had similar problem. I am able to run tests in order by nesting describe blocks as shown below. my tests are in separate files that I require here.
const puppeteer = require('puppeteer');
const login = require('./login');
const upload = require('./upload');
let browser;
beforeAll(async () => {
browser = await puppeteer.launch({
headless: false,
devtools: true,
slowMo: 50
});
})
describe('test suite', () => {
describe('login', () => {
test('url is correct', async () => {
const url = await login();
expect(url).toBe('https://uat2.onplanapp.com/#/');
}, 25000);
});
describe('upload', () => {
test('file upload ok', async () => {
url = await upload();
console.log('page.url');
expect(url).toBe('https://uat2.onplanapp.com/#/moduleLibrary');
//expect(url).toBe('https://uat2.onplanapp.com/#/uploadFile');
}, 10000);
});
afterAll(async done => {
console.log('GOT TO AFTER ALL');
browser.close()
done();
});
});
So I just started work on protractor tests and I'm facing the following problem - my tests fail inconsistently. Sometimes the test may pass and the next time it fails. Reasons to fail is very different, it may because it failed to find an element on a page or element does not have text in it (even if it has).
I'm running on Ubuntu 14.04, the same problem relevant for Chrome Version 71.0.3578.80 and Firefox Version 60.0.2. AngularJS Version 1.7.2 and Protractor Version 5.4.0. I believe the problem is somewhere in my code, so here below I provided an example of an existing code base.
Here is my protractor config
exports.config = {
rootElement: '[ng-app="myapp"]',
framework: 'jasmine',
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['./e2e/**/*protractor.js'],
SELENIUM_PROMISE_MANAGER: false,
baseUrl: 'https://localhost/',
allScriptsTimeout: 20000,
jasmineNodeOpts: {
defaultTimeoutInterval: 100000,
},
capabilities: {
browserName: 'firefox',
marionette: true,
acceptInsecureCerts: true,
'moz:firefoxOptions': {
args: ['--headless'],
},
},
}
And here capabilities for chrome browser
capabilities: {
browserName: 'chrome',
chromeOptions: {
args: [ "--headless", "--disable-gpu", "--window-size=1920,1080" ]
}
},
And finally, my test kit that failed a few times
const InsurerViewDriver = require('./insurer-view.driver');
const InsurerRefundDriver = require('./insurer-refund.driver');
const { PageDriver } = require('#utils/page');
const { NotificationsDriver } = require('#utils/service');
const moment = require('moment');
describe(InsurerViewDriver.pageUrl, () => {
beforeAll(async () => {
await InsurerViewDriver.goToPage();
});
it('- should test "Delete" button', async () => {
await InsurerViewDriver.clickDelete();
await NotificationsDriver.toBeShown('success');
await PageDriver.userToBeNavigated('#/setup/insurers');
await InsurerViewDriver.goToPage();
});
describe('Should test Refunds section', () => {
it('- should test refund list content', async () => {
expect(await InsurerRefundDriver.getTitle()).toEqual('REFUNDS');
const refunds = InsurerRefundDriver.getRefunds();
expect(await refunds.count()).toBe(1);
const firstRow = refunds.get(0);
expect(await firstRow.element(by.binding('item.name')).getText()).toEqual('Direct');
expect(await firstRow.element(by.binding('item.amount')).getText()).toEqual('$ 50.00');
expect(await firstRow.element(by.binding('item.number')).getText()).toEqual('');
expect(await firstRow.element(by.binding('item.date')).getText()).toEqual(moment().format('MMMM DD YYYY'));
});
it('- should test add refund action', async () => {
await InsurerRefundDriver.openNewRefundForm();
const NewRefundFormDriver = InsurerRefundDriver.getNewRefundForm();
await NewRefundFormDriver.setPayment(`#555555, ${moment().format('MMMM DD YYYY')} (amount: $2,000, rest: $1,500)`);
await NewRefundFormDriver.setPaymentMethod('Credit Card');
expect(await NewRefundFormDriver.getAmount()).toEqual('0');
await NewRefundFormDriver.setAmount(200.05);
await NewRefundFormDriver.setAuthorization('qwerty');
await NewRefundFormDriver.submit();
await NotificationsDriver.toBeShown('success');
const interactions = InsurerRefundDriver.getRefunds();
expect(await interactions.count()).toBe(2);
expect(await InsurerViewDriver.getInsurerTitleValue('Balance:')).toEqual('Balance: $ 2,200.05');
expect(await InsurerViewDriver.getInsurerTitleValue('Wallet:')).toEqual('Wallet: $ 4,799.95');
});
});
});
And here some functions from driver's, that I'm referencing in the test above
// PageDriver.userToBeNavigated
this.userToBeNavigated = async function(url) {
return await browser.wait(
protractor.ExpectedConditions.urlContains(url),
5000,
`Expectation failed - user to be navigated to "${url}"`
);
};
this.pageUrl = '#/insurer/33';
// InsurerViewDriver.goToPage
this.goToPage = async () => {
await browser.get(this.pageUrl);
};
// InsurerViewDriver.clickDelete()
this.clickDelete = async () => {
await $('[ng-click="$ctrl.removeInsurer()"]').click();
await DialogDriver.toBeShown('Are you sure you want to remove this entry?');
await DialogDriver.confirm();
};
// NotificationsDriver.toBeShown
this.toBeShown = async (type, text) => {
const awaitSeconds = 6;
return await browser.wait(
protractor.ExpectedConditions.presenceOf(
text ? element(by.cssContainingText('.toast-message', text)) : $(`.toast-${type}`)
),
awaitSeconds * 1000,
`${type} notification should be shown within ${awaitSeconds} sec`
);
}
// InsurerRefundDriver.getRefunds()
this.getRefunds = () => $('list-refunds-component').all(by.repeater('item in $data'));
// InsurerViewDriver.getInsurerTitleValue
this.getInsurerTitleValue = async (text) => {
return await element(by.cssContainingText('header-content p', text)).getText();
};
I can't upload the whole code here to give you better understanding because I have a lot of code till this moment, but the code provided above is the exact sample of approach I'm using everywhere, does anyone see a problem in my code? Thanks.
First of all add this block before exporting your config
process.on("unhandledRejection", ({message}) => {
console.log("\x1b[36m%s\x1b[0m", `Unhandled rejection: ${message}`);
});
this essentially colorfully logs to the console if you missed async/await anywhere, and it'll give confidence that you didn't miss anything.
Second, I would install "protractor-console" plugin, to make sure there is no errors/rejections in the browser console (i.e. exclude possibility of issues from your app side) and add to your config
plugins: [{
package: "protractor-console",
logLevels: [ "severe" ]
}]
Then the next problem that I would expect with these signs is incorrect waiting functions. Ideally you have to test them separately as you develop your e2e project, but since it's all written already I'll tell you how I debugged them. Note, this approach won't probably help you if your actions are less than a sec (i.e. you can't notice them). Otherwise follow this chain.
1) I created run configuration in WebStorm, as described in my comment here (find mine) How to debug angular protractor tests in WebStorm
2) Set a breakpoint in the first line of the test I want to debug
3) Then execute your test line by line, using the created run config.
When you start debugging process, webstorm opens up a panel with three sections: frames, console, variables. When the variables section has a message connected to localhost and no variables listed, this means your step is still being executed. Once loading completed you can see all your variables and you can execute next command. So the main principle here is you click Step Over button and watch for variables section. IF VARIABLES APPEAR BEFORE THE APPS LOADING COMPLETED (the waiting method executed, but the app is still loading, which is wrong) then you need to work on this method. By going this way I identified a lot of gaps in my custom waiting methods.
And finally if this doesn't work, please attach stack trace of your errors and ping me
I'm concerned about this code snippet
describe(InsurerViewDriver.pageUrl, () => {
beforeAll(async () => {
await InsurerViewDriver.goToPage();
});
it('- should test "Delete" button', async () => {
await InsurerViewDriver.clickDelete();
await NotificationsDriver.toBeShown('success');
await PageDriver.userToBeNavigated('#/setup/insurers');
await InsurerViewDriver.goToPage(); // WHY IS THIS HERE?
});
describe('Should test Refunds section', () => {
it('- should test refund list content', async () => {
// DOESN'T THIS NEED SOME SETUP?
expect(await InsurerRefundDriver.getTitle()).toEqual('REFUNDS');
// <truncated>
You should not depend on the first it clause to set up the suite below it. You didn't post the code for InsurerRefundDriver.getTitle() but if that code does not send the browser to the correct URL and then wait for the page to finish loading, it is a problem. You should probably have await InsurerViewDriver.goToPage(); in a beforeEach clause.
After some time research I found what was the problem. The cause was the way I'm navigate through the app.
this.goToPage = async () => {
await browser.get(this.pageUrl);
};
Turns out, that browser.get method is being resolved when url changed, but now when angularjs done compile. I used the same approach in every test kit, that's why my tests were failing inconsistently, sometimes page was not fully loaded before test start.
So here is an approach that did the trick
this.goToPage = async () => {
await browser.get(this.pageUrl);
await browser.wait(EC.presenceOf(`some important element`), 5000, 'Element did not appear after route change');
};
You should ensure that page done all the compiling job before moving on.
It seems this could be due to asynchronous javascript.
browser.ignoreSynchronization = true; has a global effect for all your tests. you may have to set it back to false, so protractor waits for angular to be finished rendering the page. e.g. in or before your second beforeEach function
I was wondering if there is a way to somehow pass a parameter to let your fixture or even all tests know which browser they are running in.
In my particular case, I would use that parameter to simply assign a corresponding value to a variable inside my tests.
For example,
switch(browser) {
case 'chrome':
chrome = 'chrome.com';
break;
case 'firefox':
link = 'firefox.com';
break;
case 'safari':
link = 'safari.com';
break;
default:
break;
}
Currently, I was able to achieve something similar by adding a global node variable and it looks something like this:
"chrome": "BROWSER=1 node runner.js"
However, this makes me create a separate runner for every browser (safari-runner, chrome-runner etc.) and I would like to have everything in one place.
So at the end of the day, I would need to make this work:
const createTestCafe = require('testcafe');
let testcafe = null;
createTestCafe('localhost', 1337, 1338)
.then(tc => {
testcafe = tc;
const runner = testcafe.createRunner();
return runner
.src('test.js')
.browsers(['all browsers'])
.run({
passBrowserId: true // I guess it would look something like this
});
})
.then(failedCount => {
console.log('Tests failed: ' + failedCount);
testcafe.close();
})
.catch(error => {
console.log(error);
testcafe.close();
});
There are several ways to get browser info:
Get navigator.userAgent from the browser using ClientFunction. Optionally you can use a module to parse an user agent string, for example: ua-parser-js.
import { ClientFunction } from 'testcafe';
import uaParser from 'ua-parser-js';
fixture `get ua`
.page `https://testcafe.devexpress.com/`;
const getUA = ClientFunction(() => navigator.userAgent);
test('get ua', async t => {
const ua = await getUA();
console.log(uaParser(ua).browser.name);
});
Use RequestLogger to obtain browser info. For example:
import { RequestLogger } from 'testcafe';
const logger = RequestLogger('https://testcafe.devexpress.com/');
fixture `test`
.page('https://testcafe.devexpress.com')
.requestHooks(logger);
test('test 1', async t => {
await t.expect(logger.contains(record => record.response.statusCode === 200)).ok();
const logRecord = logger.requests[0];
console.log(logRecord.userAgent);
});
The TestCafe team is working on the t.browserInfo function, which solves the issue in the future.
Just to update this question, testcafe has now implemented t.browser, which will allow you to check the browser.name or browser.alias to determine which browser you're running in.
import { t } from 'testcafe';
const browserIncludes = b => t.browser.name.includes(b);
const isBrowserStack = () => t.browser.alias.includes('browserstack');
fixture `test`
.page('https://testcafe.devexpress.com')
test('is Chrome?', async () => {
console.log(t.browser.name);
await t.expect(browserIncludes('Chrome').ok();
await t.expect(isBrowserStack()).notOk();
});