1655970960
It's more flexible, lightweight, optimized for Playwright, and has TypeScript support out of the box. This doesn't mean, that we stop with maintaining this package.
Running your tests using Jest & Playwright
npm install -D jest jest-playwright-preset playwright
Also you can use jest-playwright-preset
with specific playwright packages: playwright-webkit
, playwright-chromium
and playwright-firefox
npm install -D jest jest-playwright-preset playwright-firefox
Update your Jest configuration, either:
with package.json
:
"jest": {
"preset": "jest-playwright-preset"
}
or with jest.config.js
:
module.exports = {
preset: 'jest-playwright-preset',
}
And add the Jest command as in the script section of your package.json
:
{
"scripts": {
"test": "jest"
}
}
Now you can use Playwright in your tests:
beforeAll(async () => {
await page.goto('https://whatismybrowser.com/')
})
test('should display correct browser', async () => {
const browser = await page.$eval('.string-major', (el) => el.innerHTML)
expect(browser).toContain('Chrome')
})
playwright
actions can take some time for execution, because of it jest-playwright
overrides jest default timeout interval from 5 to 15 seconds. You can change this interval with testTimeout
in your jest
configuration.
It's recommend to use a separate Jest configuration jest.e2e.config.js
for jest-playwright
to gain speed improvements and by that to only use Playwright in the end-to-end tests. For that you have to use the -c
flag when calling Jest and use the testMatch
or testRegex
in your Jest config to split them.
Be sure to remove any existing testEnvironment
option from your Jest configuration. The jest-playwright-preset
preset needs to manage that option itself.
Configuration options can be specified using a jest-playwright.config.js
file at the root of your project:
// jest-playwright.config.js
module.exports = {
// Options...
}
Similar to Jest globalSetup configuration can except the export of an async function:
module.exports = async () => {
await ...
};
A custom path can be specified to the jest-playwright.config.js
file within your jest.config.js
file:
process.env.JEST_PLAYWRIGHT_CONFIG = '/path/to/jest-playwright.config.js'
Alternatively, configuration options can specified using Jest's own testEnvironmentOptions
option within your jest.config.js
file:
// jest.config.js
module.exports = {
preset: 'jest-playwright-preset',
testEnvironmentOptions: {
'jest-playwright': {
// Options...
},
},
}
launchOptions
<[object]>. All Playwright launch options can be specified in config. Since it is JavaScript, you can use all stuff you need, including environment.launchType
<LAUNCH | PERSISTENT | SERVER>. Method to launch browser instance. jest-playwright
attaches Playwright to an existing browser instance by default.connectOptions
<[object]>. All Playwright connect options can be specified in config.contextOptions
<[object]>. All Playwright context options can be specified in config.chromium
Each test runs Chromium (default).firefox
Each test runs Firefox.webkit
Each test runs Webkit.exitOnPageError
<[boolean]>. Exits process on any page error. Defaults to true
.collectCoverage
<[boolean]>. Enables the coverage collection of the saveCoverage(page)
calls to the .nyc_output/coverage.json
file.serverOptions
<[object]>. All jest-process-manager
options.selectors
<[array]>. Define selectors. Each selector must be an object with name and script properties.skipInitialization
<[boolean]>. Add you ability to skip first setup playwright
process. Possible use cases can be found hereresetContextPerTest
<[boolean]>. Option for opening a new context per testuseDefaultBrowserType
<[boolean]>. Sometimes browser
+ device
combinations don't have any sense. With this option tests will be run with defaultBrowserType
of device. Pay attention that you should define devices to correct usage of this option.You can control the browser with passing environment variable.
// jest-playwright.config.js
module.exports = {
browsers: [process.env.BROWSER],
}
For launchOptions
, connectOptions
and contextOptions
you can define special browser options.
// jest-playwright.config.js
module.exports = {
connectOptions: {
chromium: {
wsEndpoint: 'ws://chrome.proxy.com:4444'
},
firefox: {
wsEndpoint: 'ws://firefox.proxy.com:4444'
}
},
...
}
There are different ways to define devices in your configuration file:
module.exports = {
browsers: ["chromium", "webkit"],
...
}
{
// Name of browser
name: 'chromium' | 'firefox' | 'webkit'
// Display name for test
displayName: string
...
// Browser options
}
There are different ways to define devices in your configuration file:
module.exports = {
devices: ["iPhone 6", "Pixel 2"],
...
}
module.exports = {
devices: /iPhone 8/,
...
}
{
// Name of device
name: string
// Page width and height
viewport: {
width: number
height: number
}
// user agent
userAgent: string
// device scale factor
deviceScaleFactor: number
// is device is mobile
isMobile: boolean
// support of touch events
hasTouch: boolean
// device default browser
defaultBrowserType: chromium, firefox or webkit
}
browserName
<[string]> - name of the current browser (chromium, firefox or webkit)deviceName
<[string]> - name of the current devicebrowser
<[Browser]> - Playwright browser instancecontext
<[Context]> - a new Playwright context instance for each new test filepage
<[Page]> - Playwright page instance (since a new context for every test file also creates a new page for it)All of them are available globally in each Jest test. If you are using ESLint and JavaScript, its recommend to use it in combination with the eslint-plugin-jest-playwright.
Playwright give you ability to configure the browser for debugging with the PWDEBUG
environment variable. It will launch the browser in headful mode, disables playwright timeout and Jest won't timeout anymore.:
PWDEBUG=1 jest
beforeEach(async () => {
await jestPlaywright.resetPage()
})
To create a new page for each test, you can use this snippet to have a new page object for each individual test.
beforeEach(async () => {
await jestPlaywright.resetContext()
})
To create a new context for each test, you can use this snippet to have a new context object for each individual test.
beforeEach(async () => {
await jestPlaywright.resetBrowser()
})
You can use this snippet to reset current browser for each individual test. It will reset browser, context and page.
jest-playwright
provides some functions to debug your tests.
IMPORTANT NOTE: For these kind of tests you should use properties passed through callback function instead of globals
This helper function provide you ability to run specific tests in debug
mode. It will disable headless
mode. You can find more information here
test.jestPlaywrightDebug('failed', async ({ page }) => {
await page.goto('https://github.com/')
const title = await page.title()
await expect(title).toBe('Google')
})
Also you can define options for debug
mode with debugOptions
:
// jest-playwright.config.js
module.exports = {
debugOptions: {
...
contextOptions: {
offline: true
}
}
...
}
This helper function provide you ability to run specific tests with passed options. You can define browser
and device
properties to run test for them, otherwise test run for current configuration.
test.jestPlaywrightConfig(
{
// your jest-playwright options
},
'test name',
async ({ browser, context, page }) => {
/* ... */
},
)
It's possible to track the coverage of the end-to-end tests with the babel-plugin-istanbul Babel plugin configured. It needs to be included in the web application which you are gonna test otherwise it won't work. To use it, you have to set collectCoverage
in the jest-playwright.config.js
to true
. Per default the test coverage will be automatically saved after each navigation change (beforeunload
event). If a certain code path is not covered, you can manually call and add the corresponding saveCoverage(page)
call to your tests like that:
await jestPlaywright.saveCoverage(page)
By using coverage collection, it will write the coverage data to the .nyc_output/coverage.json
file which can be transformed using nyc
to the lcov format:
npx nyc report --reporter=lcovonly
or to HTML:
npx nyc report --reporter=html
which will create a HTML website in the coverage
directory.
It's possible to skip tests for browsers or combination of browsers and devices
it.jestPlaywrightSkip(
{ browsers: ['chromium'] },
'should skip this one',
async () => {
const title = await page.title()
expect(title).toBe('Google')
},
)
Playwright engine pierces open shadow DOM by default.
beforeAll(async () => {
await page.goto(
'https://mdn.github.io/web-components-examples/popup-info-box-web-component/',
)
})
test('should display "google" text on page', async () => {
const shadowElem = await page.$('.info')
const shadowElemText = await shadowElem.innerHTML()
expect(shadowElemText).toBe(
'Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card.',
)
})
Jest Playwright integrates a functionality to start a server when running your test suite, like jest-puppeteer. It automatically closes the server when tests are done.
To use it, specify a server section in your jest-playwright.config.js
.
// jest-playwright.config.js
module.exports = {
serverOptions: {
command: 'node server.js',
port: 4444,
},
}
Other options are documented in jest-process-manager.
The default jest-playwright environment is node, but you can use a browser-like environment through jest-playwright-jsdom
There is a utility package expect-playwright which simplifies the expect statements in combination with Playwright to make e.g. shorter text comparisons.
'page' is not defined
There is an ESLint plugin available eslint-plugin-jest-playwright available which includes the globals for using jest-playwright.
You can run tests for multiple browsers and devices:
jest-playwright.config.js
:module.exports = {
browsers: ["chromium", "webkit"],
devices: ["iPhone 6", "Pixel 2"],
...
}
It will run your tests for:
If there is no defined browsers in config it will run tests for chromium browser.
You can use jest-playwright with custom test environment for taking screenshots during test failures for example:
jest.config.json
"testEnvironment": "./CustomEnvironment.js"
CustomEnvironment.js
const PlaywrightEnvironment = require('jest-playwright-preset/lib/PlaywrightEnvironment')
.default
class CustomEnvironment extends PlaywrightEnvironment {
async setup() {
await super.setup()
// Your setup
}
async teardown() {
// Your teardown
await super.teardown()
}
async handleTestEvent(event) {
await super.handleTestEvent(event);
if (event.name === 'test_done' && event.test.errors.length > 0) {
const parentName = event.test.parent.name.replace(/\W/g, '-')
const specName = event.test.name.replace(/\W/g, '-')
await this.global.page.screenshot({
path: `screenshots/${parentName}_${specName}.png`,
})
}
}
}
module.exports = CustomEnvironment
jest-playwright using custom runner underhood. So if you need implement your own runner
, you should extend it:
jest.config.json
"runner": "./CustomRunner.js"
CustomRunner.js
const PlaywrightRunner = require('jest-playwright-preset/lib/PlaywrightRunner')
.default
class CustomRunner extends PlaywrightRunner {
constructor(...args) {
super(...args)
this.isSerial = true
}
}
module.exports = CustomRunner
globalSetup
and globalTeardown
For this use case, jest-playwright-preset
exposes two methods: globalSetup
and globalTeardown
, so that you can wrap them with your own global setup and global teardown methods as the following example:
// global-setup.js
import { globalSetup as playwrightGlobalSetup } from 'jest-playwright-preset';
module.exports = async function globalSetup(globalConfig) {
await playwrightGlobalSetup(globalConfig);
const browserServer = await chromium.launchServer();
const wsEndpoint = browserServer.wsEndpoint();
const browser = await chromium.connect({ wsEndpoint: wsEndpoint });
const page = await browser.newPage();
// your login function
await doLogin(page);
// store authentication data
const storage = await page.context().storageState();
process.env.STORAGE = JSON.stringify(storage);
};
// global-teardown.js
import { globalTeardown as playwrightGlobalTeardown } from 'jest-playwright-preset';
module.exports = async function globalTeardown(globalConfig) {
// Your global teardown
await playwrightGlobalTeardown(globalConfig);
}
Then assigning your js file paths to the globalSetup
and globalTeardown
property in your Jest configuration.
{
// ...
"globalSetup": "./global-setup.js",
"globalTeardown": "./global-teardown.js"
}
Now your custom globalSetup
and globalTeardown
will be triggered once before and after all test suites.
Example Jest configuration in combination with ts-jest:
module.exports = {
preset: 'jest-playwright-preset',
transform: {
'^.+\\.ts$': 'ts-jest',
},
}
Types are also available, which you can either use via directly in your test:
/// <reference types="jest-playwright-preset" />
/// <reference types="expect-playwright" />
or at your central tsconfig.json
either via files
:
{
"files": [
"./global.d.ts",
"node_modules/jest-playwright-preset/types/global.d.ts",
"node_modules/expect-playwright/global.d.ts"
]
}
or via types
:
{
"compilerOptions": {
"types": ["jest-playwright-preset", "expect-playwright"]
}
}
It's important to not change the testEnvironment
to node
. Otherwise it won't work.
If you face into error messages like
UnhandledPromiseRejectionWarning: Error: Protocol error (Runtime.callFunctionOn): Target closed.
or
Timeout - Async callback was not invoked within the 20000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000ms timeout specified by jest.setTimeout.Error:
and your Jest error reporting will only show that an entire test (it()
function) has failed, then you need to increase the Jest timeout because the Playwright timeout is greater than the Jest timeout. So Jest in the end will simply stop the execution and no verbose (which exact line) error reporting can be generated.
To fix this behavior simply call
jest.setTimeout(35 * 1000)
in your tests at the top. (30 seconds is the default Playwright timeout for waiting for an specific element.)
If for your individual tests a new entire browser instance spins up each time and it won't be reused, then you probably run them in parallel. If you run them in a synchronous way with the --runInBand
CLI option for Jest, then the same browser instance will be re-used and this should fix the issue.
Demonstration the usage of jest-playwright
for various test cases can be found in playwright-jest-examples
Thanks to Smooth Code for the great jest-puppeteer.
Author: Playwright-community
Source Code: https://github.com/playwright-community/jest-playwright/
License: MIT license
#node #jest #playwright #testing #typescript
1667425440
Perl script converts PDF files to Gerber format
Pdf2Gerb generates Gerber 274X photoplotting and Excellon drill files from PDFs of a PCB. Up to three PDFs are used: the top copper layer, the bottom copper layer (for 2-sided PCBs), and an optional silk screen layer. The PDFs can be created directly from any PDF drawing software, or a PDF print driver can be used to capture the Print output if the drawing software does not directly support output to PDF.
The general workflow is as follows:
Please note that Pdf2Gerb does NOT perform DRC (Design Rule Checks), as these will vary according to individual PCB manufacturer conventions and capabilities. Also note that Pdf2Gerb is not perfect, so the output files must always be checked before submitting them. As of version 1.6, Pdf2Gerb supports most PCB elements, such as round and square pads, round holes, traces, SMD pads, ground planes, no-fill areas, and panelization. However, because it interprets the graphical output of a Print function, there are limitations in what it can recognize (or there may be bugs).
See docs/Pdf2Gerb.pdf for install/setup, config, usage, and other info.
#Pdf2Gerb config settings:
#Put this file in same folder/directory as pdf2gerb.pl itself (global settings),
#or copy to another folder/directory with PDFs if you want PCB-specific settings.
#There is only one user of this file, so we don't need a custom package or namespace.
#NOTE: all constants defined in here will be added to main namespace.
#package pdf2gerb_cfg;
use strict; #trap undef vars (easier debug)
use warnings; #other useful info (easier debug)
##############################################################################################
#configurable settings:
#change values here instead of in main pfg2gerb.pl file
use constant WANT_COLORS => ($^O !~ m/Win/); #ANSI colors no worky on Windows? this must be set < first DebugPrint() call
#just a little warning; set realistic expectations:
#DebugPrint("${\(CYAN)}Pdf2Gerb.pl ${\(VERSION)}, $^O O/S\n${\(YELLOW)}${\(BOLD)}${\(ITALIC)}This is EXPERIMENTAL software. \nGerber files MAY CONTAIN ERRORS. Please CHECK them before fabrication!${\(RESET)}", 0); #if WANT_DEBUG
use constant METRIC => FALSE; #set to TRUE for metric units (only affect final numbers in output files, not internal arithmetic)
use constant APERTURE_LIMIT => 0; #34; #max #apertures to use; generate warnings if too many apertures are used (0 to not check)
use constant DRILL_FMT => '2.4'; #'2.3'; #'2.4' is the default for PCB fab; change to '2.3' for CNC
use constant WANT_DEBUG => 0; #10; #level of debug wanted; higher == more, lower == less, 0 == none
use constant GERBER_DEBUG => 0; #level of debug to include in Gerber file; DON'T USE FOR FABRICATION
use constant WANT_STREAMS => FALSE; #TRUE; #save decompressed streams to files (for debug)
use constant WANT_ALLINPUT => FALSE; #TRUE; #save entire input stream (for debug ONLY)
#DebugPrint(sprintf("${\(CYAN)}DEBUG: stdout %d, gerber %d, want streams? %d, all input? %d, O/S: $^O, Perl: $]${\(RESET)}\n", WANT_DEBUG, GERBER_DEBUG, WANT_STREAMS, WANT_ALLINPUT), 1);
#DebugPrint(sprintf("max int = %d, min int = %d\n", MAXINT, MININT), 1);
#define standard trace and pad sizes to reduce scaling or PDF rendering errors:
#This avoids weird aperture settings and replaces them with more standardized values.
#(I'm not sure how photoplotters handle strange sizes).
#Fewer choices here gives more accurate mapping in the final Gerber files.
#units are in inches
use constant TOOL_SIZES => #add more as desired
(
#round or square pads (> 0) and drills (< 0):
.010, -.001, #tiny pads for SMD; dummy drill size (too small for practical use, but needed so StandardTool will use this entry)
.031, -.014, #used for vias
.041, -.020, #smallest non-filled plated hole
.051, -.025,
.056, -.029, #useful for IC pins
.070, -.033,
.075, -.040, #heavier leads
# .090, -.043, #NOTE: 600 dpi is not high enough resolution to reliably distinguish between .043" and .046", so choose 1 of the 2 here
.100, -.046,
.115, -.052,
.130, -.061,
.140, -.067,
.150, -.079,
.175, -.088,
.190, -.093,
.200, -.100,
.220, -.110,
.160, -.125, #useful for mounting holes
#some additional pad sizes without holes (repeat a previous hole size if you just want the pad size):
.090, -.040, #want a .090 pad option, but use dummy hole size
.065, -.040, #.065 x .065 rect pad
.035, -.040, #.035 x .065 rect pad
#traces:
.001, #too thin for real traces; use only for board outlines
.006, #minimum real trace width; mainly used for text
.008, #mainly used for mid-sized text, not traces
.010, #minimum recommended trace width for low-current signals
.012,
.015, #moderate low-voltage current
.020, #heavier trace for power, ground (even if a lighter one is adequate)
.025,
.030, #heavy-current traces; be careful with these ones!
.040,
.050,
.060,
.080,
.100,
.120,
);
#Areas larger than the values below will be filled with parallel lines:
#This cuts down on the number of aperture sizes used.
#Set to 0 to always use an aperture or drill, regardless of size.
use constant { MAX_APERTURE => max((TOOL_SIZES)) + .004, MAX_DRILL => -min((TOOL_SIZES)) + .004 }; #max aperture and drill sizes (plus a little tolerance)
#DebugPrint(sprintf("using %d standard tool sizes: %s, max aper %.3f, max drill %.3f\n", scalar((TOOL_SIZES)), join(", ", (TOOL_SIZES)), MAX_APERTURE, MAX_DRILL), 1);
#NOTE: Compare the PDF to the original CAD file to check the accuracy of the PDF rendering and parsing!
#for example, the CAD software I used generated the following circles for holes:
#CAD hole size: parsed PDF diameter: error:
# .014 .016 +.002
# .020 .02267 +.00267
# .025 .026 +.001
# .029 .03167 +.00267
# .033 .036 +.003
# .040 .04267 +.00267
#This was usually ~ .002" - .003" too big compared to the hole as displayed in the CAD software.
#To compensate for PDF rendering errors (either during CAD Print function or PDF parsing logic), adjust the values below as needed.
#units are pixels; for example, a value of 2.4 at 600 dpi = .0004 inch, 2 at 600 dpi = .0033"
use constant
{
HOLE_ADJUST => -0.004 * 600, #-2.6, #holes seemed to be slightly oversized (by .002" - .004"), so shrink them a little
RNDPAD_ADJUST => -0.003 * 600, #-2, #-2.4, #round pads seemed to be slightly oversized, so shrink them a little
SQRPAD_ADJUST => +0.001 * 600, #+.5, #square pads are sometimes too small by .00067, so bump them up a little
RECTPAD_ADJUST => 0, #(pixels) rectangular pads seem to be okay? (not tested much)
TRACE_ADJUST => 0, #(pixels) traces seemed to be okay?
REDUCE_TOLERANCE => .001, #(inches) allow this much variation when reducing circles and rects
};
#Also, my CAD's Print function or the PDF print driver I used was a little off for circles, so define some additional adjustment values here:
#Values are added to X/Y coordinates; units are pixels; for example, a value of 1 at 600 dpi would be ~= .002 inch
use constant
{
CIRCLE_ADJUST_MINX => 0,
CIRCLE_ADJUST_MINY => -0.001 * 600, #-1, #circles were a little too high, so nudge them a little lower
CIRCLE_ADJUST_MAXX => +0.001 * 600, #+1, #circles were a little too far to the left, so nudge them a little to the right
CIRCLE_ADJUST_MAXY => 0,
SUBST_CIRCLE_CLIPRECT => FALSE, #generate circle and substitute for clip rects (to compensate for the way some CAD software draws circles)
WANT_CLIPRECT => TRUE, #FALSE, #AI doesn't need clip rect at all? should be on normally?
RECT_COMPLETION => FALSE, #TRUE, #fill in 4th side of rect when 3 sides found
};
#allow .012 clearance around pads for solder mask:
#This value effectively adjusts pad sizes in the TOOL_SIZES list above (only for solder mask layers).
use constant SOLDER_MARGIN => +.012; #units are inches
#line join/cap styles:
use constant
{
CAP_NONE => 0, #butt (none); line is exact length
CAP_ROUND => 1, #round cap/join; line overhangs by a semi-circle at either end
CAP_SQUARE => 2, #square cap/join; line overhangs by a half square on either end
CAP_OVERRIDE => FALSE, #cap style overrides drawing logic
};
#number of elements in each shape type:
use constant
{
RECT_SHAPELEN => 6, #x0, y0, x1, y1, count, "rect" (start, end corners)
LINE_SHAPELEN => 6, #x0, y0, x1, y1, count, "line" (line seg)
CURVE_SHAPELEN => 10, #xstart, ystart, x0, y0, x1, y1, xend, yend, count, "curve" (bezier 2 points)
CIRCLE_SHAPELEN => 5, #x, y, 5, count, "circle" (center + radius)
};
#const my %SHAPELEN =
#Readonly my %SHAPELEN =>
our %SHAPELEN =
(
rect => RECT_SHAPELEN,
line => LINE_SHAPELEN,
curve => CURVE_SHAPELEN,
circle => CIRCLE_SHAPELEN,
);
#panelization:
#This will repeat the entire body the number of times indicated along the X or Y axes (files grow accordingly).
#Display elements that overhang PCB boundary can be squashed or left as-is (typically text or other silk screen markings).
#Set "overhangs" TRUE to allow overhangs, FALSE to truncate them.
#xpad and ypad allow margins to be added around outer edge of panelized PCB.
use constant PANELIZE => {'x' => 1, 'y' => 1, 'xpad' => 0, 'ypad' => 0, 'overhangs' => TRUE}; #number of times to repeat in X and Y directions
# Set this to 1 if you need TurboCAD support.
#$turboCAD = FALSE; #is this still needed as an option?
#CIRCAD pad generation uses an appropriate aperture, then moves it (stroke) "a little" - we use this to find pads and distinguish them from PCB holes.
use constant PAD_STROKE => 0.3; #0.0005 * 600; #units are pixels
#convert very short traces to pads or holes:
use constant TRACE_MINLEN => .001; #units are inches
#use constant ALWAYS_XY => TRUE; #FALSE; #force XY even if X or Y doesn't change; NOTE: needs to be TRUE for all pads to show in FlatCAM and ViewPlot
use constant REMOVE_POLARITY => FALSE; #TRUE; #set to remove subtractive (negative) polarity; NOTE: must be FALSE for ground planes
#PDF uses "points", each point = 1/72 inch
#combined with a PDF scale factor of .12, this gives 600 dpi resolution (1/72 * .12 = 600 dpi)
use constant INCHES_PER_POINT => 1/72; #0.0138888889; #multiply point-size by this to get inches
# The precision used when computing a bezier curve. Higher numbers are more precise but slower (and generate larger files).
#$bezierPrecision = 100;
use constant BEZIER_PRECISION => 36; #100; #use const; reduced for faster rendering (mainly used for silk screen and thermal pads)
# Ground planes and silk screen or larger copper rectangles or circles are filled line-by-line using this resolution.
use constant FILL_WIDTH => .01; #fill at most 0.01 inch at a time
# The max number of characters to read into memory
use constant MAX_BYTES => 10 * M; #bumped up to 10 MB, use const
use constant DUP_DRILL1 => TRUE; #FALSE; #kludge: ViewPlot doesn't load drill files that are too small so duplicate first tool
my $runtime = time(); #Time::HiRes::gettimeofday(); #measure my execution time
print STDERR "Loaded config settings from '${\(__FILE__)}'.\n";
1; #last value must be truthful to indicate successful load
#############################################################################################
#junk/experiment:
#use Package::Constants;
#use Exporter qw(import); #https://perldoc.perl.org/Exporter.html
#my $caller = "pdf2gerb::";
#sub cfg
#{
# my $proto = shift;
# my $class = ref($proto) || $proto;
# my $settings =
# {
# $WANT_DEBUG => 990, #10; #level of debug wanted; higher == more, lower == less, 0 == none
# };
# bless($settings, $class);
# return $settings;
#}
#use constant HELLO => "hi there2"; #"main::HELLO" => "hi there";
#use constant GOODBYE => 14; #"main::GOODBYE" => 12;
#print STDERR "read cfg file\n";
#our @EXPORT_OK = Package::Constants->list(__PACKAGE__); #https://www.perlmonks.org/?node_id=1072691; NOTE: "_OK" skips short/common names
#print STDERR scalar(@EXPORT_OK) . " consts exported:\n";
#foreach(@EXPORT_OK) { print STDERR "$_\n"; }
#my $val = main::thing("xyz");
#print STDERR "caller gave me $val\n";
#foreach my $arg (@ARGV) { print STDERR "arg $arg\n"; }
Author: swannman
Source Code: https://github.com/swannman/pdf2gerb
License: GPL-3.0 license
1655970960
It's more flexible, lightweight, optimized for Playwright, and has TypeScript support out of the box. This doesn't mean, that we stop with maintaining this package.
Running your tests using Jest & Playwright
npm install -D jest jest-playwright-preset playwright
Also you can use jest-playwright-preset
with specific playwright packages: playwright-webkit
, playwright-chromium
and playwright-firefox
npm install -D jest jest-playwright-preset playwright-firefox
Update your Jest configuration, either:
with package.json
:
"jest": {
"preset": "jest-playwright-preset"
}
or with jest.config.js
:
module.exports = {
preset: 'jest-playwright-preset',
}
And add the Jest command as in the script section of your package.json
:
{
"scripts": {
"test": "jest"
}
}
Now you can use Playwright in your tests:
beforeAll(async () => {
await page.goto('https://whatismybrowser.com/')
})
test('should display correct browser', async () => {
const browser = await page.$eval('.string-major', (el) => el.innerHTML)
expect(browser).toContain('Chrome')
})
playwright
actions can take some time for execution, because of it jest-playwright
overrides jest default timeout interval from 5 to 15 seconds. You can change this interval with testTimeout
in your jest
configuration.
It's recommend to use a separate Jest configuration jest.e2e.config.js
for jest-playwright
to gain speed improvements and by that to only use Playwright in the end-to-end tests. For that you have to use the -c
flag when calling Jest and use the testMatch
or testRegex
in your Jest config to split them.
Be sure to remove any existing testEnvironment
option from your Jest configuration. The jest-playwright-preset
preset needs to manage that option itself.
Configuration options can be specified using a jest-playwright.config.js
file at the root of your project:
// jest-playwright.config.js
module.exports = {
// Options...
}
Similar to Jest globalSetup configuration can except the export of an async function:
module.exports = async () => {
await ...
};
A custom path can be specified to the jest-playwright.config.js
file within your jest.config.js
file:
process.env.JEST_PLAYWRIGHT_CONFIG = '/path/to/jest-playwright.config.js'
Alternatively, configuration options can specified using Jest's own testEnvironmentOptions
option within your jest.config.js
file:
// jest.config.js
module.exports = {
preset: 'jest-playwright-preset',
testEnvironmentOptions: {
'jest-playwright': {
// Options...
},
},
}
launchOptions
<[object]>. All Playwright launch options can be specified in config. Since it is JavaScript, you can use all stuff you need, including environment.launchType
<LAUNCH | PERSISTENT | SERVER>. Method to launch browser instance. jest-playwright
attaches Playwright to an existing browser instance by default.connectOptions
<[object]>. All Playwright connect options can be specified in config.contextOptions
<[object]>. All Playwright context options can be specified in config.chromium
Each test runs Chromium (default).firefox
Each test runs Firefox.webkit
Each test runs Webkit.exitOnPageError
<[boolean]>. Exits process on any page error. Defaults to true
.collectCoverage
<[boolean]>. Enables the coverage collection of the saveCoverage(page)
calls to the .nyc_output/coverage.json
file.serverOptions
<[object]>. All jest-process-manager
options.selectors
<[array]>. Define selectors. Each selector must be an object with name and script properties.skipInitialization
<[boolean]>. Add you ability to skip first setup playwright
process. Possible use cases can be found hereresetContextPerTest
<[boolean]>. Option for opening a new context per testuseDefaultBrowserType
<[boolean]>. Sometimes browser
+ device
combinations don't have any sense. With this option tests will be run with defaultBrowserType
of device. Pay attention that you should define devices to correct usage of this option.You can control the browser with passing environment variable.
// jest-playwright.config.js
module.exports = {
browsers: [process.env.BROWSER],
}
For launchOptions
, connectOptions
and contextOptions
you can define special browser options.
// jest-playwright.config.js
module.exports = {
connectOptions: {
chromium: {
wsEndpoint: 'ws://chrome.proxy.com:4444'
},
firefox: {
wsEndpoint: 'ws://firefox.proxy.com:4444'
}
},
...
}
There are different ways to define devices in your configuration file:
module.exports = {
browsers: ["chromium", "webkit"],
...
}
{
// Name of browser
name: 'chromium' | 'firefox' | 'webkit'
// Display name for test
displayName: string
...
// Browser options
}
There are different ways to define devices in your configuration file:
module.exports = {
devices: ["iPhone 6", "Pixel 2"],
...
}
module.exports = {
devices: /iPhone 8/,
...
}
{
// Name of device
name: string
// Page width and height
viewport: {
width: number
height: number
}
// user agent
userAgent: string
// device scale factor
deviceScaleFactor: number
// is device is mobile
isMobile: boolean
// support of touch events
hasTouch: boolean
// device default browser
defaultBrowserType: chromium, firefox or webkit
}
browserName
<[string]> - name of the current browser (chromium, firefox or webkit)deviceName
<[string]> - name of the current devicebrowser
<[Browser]> - Playwright browser instancecontext
<[Context]> - a new Playwright context instance for each new test filepage
<[Page]> - Playwright page instance (since a new context for every test file also creates a new page for it)All of them are available globally in each Jest test. If you are using ESLint and JavaScript, its recommend to use it in combination with the eslint-plugin-jest-playwright.
Playwright give you ability to configure the browser for debugging with the PWDEBUG
environment variable. It will launch the browser in headful mode, disables playwright timeout and Jest won't timeout anymore.:
PWDEBUG=1 jest
beforeEach(async () => {
await jestPlaywright.resetPage()
})
To create a new page for each test, you can use this snippet to have a new page object for each individual test.
beforeEach(async () => {
await jestPlaywright.resetContext()
})
To create a new context for each test, you can use this snippet to have a new context object for each individual test.
beforeEach(async () => {
await jestPlaywright.resetBrowser()
})
You can use this snippet to reset current browser for each individual test. It will reset browser, context and page.
jest-playwright
provides some functions to debug your tests.
IMPORTANT NOTE: For these kind of tests you should use properties passed through callback function instead of globals
This helper function provide you ability to run specific tests in debug
mode. It will disable headless
mode. You can find more information here
test.jestPlaywrightDebug('failed', async ({ page }) => {
await page.goto('https://github.com/')
const title = await page.title()
await expect(title).toBe('Google')
})
Also you can define options for debug
mode with debugOptions
:
// jest-playwright.config.js
module.exports = {
debugOptions: {
...
contextOptions: {
offline: true
}
}
...
}
This helper function provide you ability to run specific tests with passed options. You can define browser
and device
properties to run test for them, otherwise test run for current configuration.
test.jestPlaywrightConfig(
{
// your jest-playwright options
},
'test name',
async ({ browser, context, page }) => {
/* ... */
},
)
It's possible to track the coverage of the end-to-end tests with the babel-plugin-istanbul Babel plugin configured. It needs to be included in the web application which you are gonna test otherwise it won't work. To use it, you have to set collectCoverage
in the jest-playwright.config.js
to true
. Per default the test coverage will be automatically saved after each navigation change (beforeunload
event). If a certain code path is not covered, you can manually call and add the corresponding saveCoverage(page)
call to your tests like that:
await jestPlaywright.saveCoverage(page)
By using coverage collection, it will write the coverage data to the .nyc_output/coverage.json
file which can be transformed using nyc
to the lcov format:
npx nyc report --reporter=lcovonly
or to HTML:
npx nyc report --reporter=html
which will create a HTML website in the coverage
directory.
It's possible to skip tests for browsers or combination of browsers and devices
it.jestPlaywrightSkip(
{ browsers: ['chromium'] },
'should skip this one',
async () => {
const title = await page.title()
expect(title).toBe('Google')
},
)
Playwright engine pierces open shadow DOM by default.
beforeAll(async () => {
await page.goto(
'https://mdn.github.io/web-components-examples/popup-info-box-web-component/',
)
})
test('should display "google" text on page', async () => {
const shadowElem = await page.$('.info')
const shadowElemText = await shadowElem.innerHTML()
expect(shadowElemText).toBe(
'Your card validation code (CVC) is an extra security feature — it is the last 3 or 4 numbers on the back of your card.',
)
})
Jest Playwright integrates a functionality to start a server when running your test suite, like jest-puppeteer. It automatically closes the server when tests are done.
To use it, specify a server section in your jest-playwright.config.js
.
// jest-playwright.config.js
module.exports = {
serverOptions: {
command: 'node server.js',
port: 4444,
},
}
Other options are documented in jest-process-manager.
The default jest-playwright environment is node, but you can use a browser-like environment through jest-playwright-jsdom
There is a utility package expect-playwright which simplifies the expect statements in combination with Playwright to make e.g. shorter text comparisons.
'page' is not defined
There is an ESLint plugin available eslint-plugin-jest-playwright available which includes the globals for using jest-playwright.
You can run tests for multiple browsers and devices:
jest-playwright.config.js
:module.exports = {
browsers: ["chromium", "webkit"],
devices: ["iPhone 6", "Pixel 2"],
...
}
It will run your tests for:
If there is no defined browsers in config it will run tests for chromium browser.
You can use jest-playwright with custom test environment for taking screenshots during test failures for example:
jest.config.json
"testEnvironment": "./CustomEnvironment.js"
CustomEnvironment.js
const PlaywrightEnvironment = require('jest-playwright-preset/lib/PlaywrightEnvironment')
.default
class CustomEnvironment extends PlaywrightEnvironment {
async setup() {
await super.setup()
// Your setup
}
async teardown() {
// Your teardown
await super.teardown()
}
async handleTestEvent(event) {
await super.handleTestEvent(event);
if (event.name === 'test_done' && event.test.errors.length > 0) {
const parentName = event.test.parent.name.replace(/\W/g, '-')
const specName = event.test.name.replace(/\W/g, '-')
await this.global.page.screenshot({
path: `screenshots/${parentName}_${specName}.png`,
})
}
}
}
module.exports = CustomEnvironment
jest-playwright using custom runner underhood. So if you need implement your own runner
, you should extend it:
jest.config.json
"runner": "./CustomRunner.js"
CustomRunner.js
const PlaywrightRunner = require('jest-playwright-preset/lib/PlaywrightRunner')
.default
class CustomRunner extends PlaywrightRunner {
constructor(...args) {
super(...args)
this.isSerial = true
}
}
module.exports = CustomRunner
globalSetup
and globalTeardown
For this use case, jest-playwright-preset
exposes two methods: globalSetup
and globalTeardown
, so that you can wrap them with your own global setup and global teardown methods as the following example:
// global-setup.js
import { globalSetup as playwrightGlobalSetup } from 'jest-playwright-preset';
module.exports = async function globalSetup(globalConfig) {
await playwrightGlobalSetup(globalConfig);
const browserServer = await chromium.launchServer();
const wsEndpoint = browserServer.wsEndpoint();
const browser = await chromium.connect({ wsEndpoint: wsEndpoint });
const page = await browser.newPage();
// your login function
await doLogin(page);
// store authentication data
const storage = await page.context().storageState();
process.env.STORAGE = JSON.stringify(storage);
};
// global-teardown.js
import { globalTeardown as playwrightGlobalTeardown } from 'jest-playwright-preset';
module.exports = async function globalTeardown(globalConfig) {
// Your global teardown
await playwrightGlobalTeardown(globalConfig);
}
Then assigning your js file paths to the globalSetup
and globalTeardown
property in your Jest configuration.
{
// ...
"globalSetup": "./global-setup.js",
"globalTeardown": "./global-teardown.js"
}
Now your custom globalSetup
and globalTeardown
will be triggered once before and after all test suites.
Example Jest configuration in combination with ts-jest:
module.exports = {
preset: 'jest-playwright-preset',
transform: {
'^.+\\.ts$': 'ts-jest',
},
}
Types are also available, which you can either use via directly in your test:
/// <reference types="jest-playwright-preset" />
/// <reference types="expect-playwright" />
or at your central tsconfig.json
either via files
:
{
"files": [
"./global.d.ts",
"node_modules/jest-playwright-preset/types/global.d.ts",
"node_modules/expect-playwright/global.d.ts"
]
}
or via types
:
{
"compilerOptions": {
"types": ["jest-playwright-preset", "expect-playwright"]
}
}
It's important to not change the testEnvironment
to node
. Otherwise it won't work.
If you face into error messages like
UnhandledPromiseRejectionWarning: Error: Protocol error (Runtime.callFunctionOn): Target closed.
or
Timeout - Async callback was not invoked within the 20000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 20000ms timeout specified by jest.setTimeout.Error:
and your Jest error reporting will only show that an entire test (it()
function) has failed, then you need to increase the Jest timeout because the Playwright timeout is greater than the Jest timeout. So Jest in the end will simply stop the execution and no verbose (which exact line) error reporting can be generated.
To fix this behavior simply call
jest.setTimeout(35 * 1000)
in your tests at the top. (30 seconds is the default Playwright timeout for waiting for an specific element.)
If for your individual tests a new entire browser instance spins up each time and it won't be reused, then you probably run them in parallel. If you run them in a synchronous way with the --runInBand
CLI option for Jest, then the same browser instance will be re-used and this should fix the issue.
Demonstration the usage of jest-playwright
for various test cases can be found in playwright-jest-examples
Thanks to Smooth Code for the great jest-puppeteer.
Author: Playwright-community
Source Code: https://github.com/playwright-community/jest-playwright/
License: MIT license
1656302303
Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API. Playwright is built to enable cross-browser web automation that is ever-green, capable, reliable and fast.
Why you might want to use Playwright Test as a test runner for unit tests, and how to fill in missing pieces (e.g. code coverage).
In this blog post, I want to
I had used "traditional" test runners before, mainly Jest, but also Mocha a few years ago.
I think there are some advantages of using Playwright for unit tests:
Same tool for E2E tests and unit tests: One tool less in your setup means one tool less to learn, configure, update, etc.
Out-of-the-box support for TypeScript: Playwright Test supports TypeScript out-of-the-box.
For all other test runners I am aware of you have to configure some transform (Babel, ts-jest
, etc.), or run the test runner with ts-node
, or compile the test cases before running them (see e.g. "Using Typescript" of the Jest docs).
Access to features unique to Playwright: Playwright has some very useful features, e.g.:
"fixtures" allow to prepare resources per test case on an "opt-in" basis.
This is in my opinion much better than the beforeEach
/ afterEach
fiddling you have to do with other test runners.
// base.ts
import { test as base } from '@playwright/test';
import * as sinon from 'sinon';
export const test = base.extend<{ fakeClock: sinon.SinonFakeTimers }>({
fakeClock: [
async ({}, use) => {
const clock = sinon.useFakeTimers();
await use(clock);
clock.restore();
},
{ scope: 'test' },
],
});
// FILE: bar.spec.ts
import { test } from './base';
test('with real clock', ({}) => {
/*
* this test case does not use the "fakeClock" fixture,
* thus the fixture does not get set up
*/
});
test('with fake clock', ({ fakeClock }) => {
/*
* fake timers will get set up for this test case
* because "fakeClock" fixture is used
*/
});
"projects" allow to run "the same or different tests in multiple configurations".
For example a while ago I had to make sure that some client/server interaction based on socket.io works with both WebSockets and HTTP long-polling.
Setting up two Playwright projects with different project parameters allowed me to run the same tests with two configurations, once using WebSockets and once using HTTP long-polling:
// playwright.config.ts
import { PlaywrightTestConfig } from '@playwright/test';
interface TestOptions {
transport: 'websocket' | 'polling';
}
const config: PlaywrightTestConfig<TestOptions> = {
projects: [
{
name: 'using transport: websocket',
use: { transport: 'websocket' },
},
{
name: 'using transport: polling',
use: { transport: 'polling' },
},
],
};
export default config;
// FILE: bar.spec.ts
import { test } from '@playwright/test';
test('socket.io connection', ({ transport }) => {
/*
* this test case will get run twice,
* with "transport" set once to "websocket" and once to "polling"
*/
initializeSocket(transport);
// ...
});
Development funded by a big company: Playwright is a product of Microsoft and has been maintained very actively over the last couple of years.
No use of globals: Playwright does not use any globals for its test runner. You import from @playwright/test
just what you need in your test files:
// FILE bar.spec.ts
import { expect, test } from '@playwright/test';
test.describe('describe title', () => {
test('test title', ({}) => {
expect(1 + 1).toEqual(2);
});
});
Compare that to existing test runners like Jest and Mocha (and Cypress, using Mocha under-the-hood). Those test runners set up their test functions as globals (it
, expect
, describe
, beforeEach
etc.).
In TypeScript projects, as soon as you install any two of these test libraries you end up with TypeScript compilation errors, since the types of those functions are usually not compatible with each other. There are a couple of workarounds to avoid those compilation errors, for example fiddling with (multiple) tsconfig.json
files such that types of these libraries are excluded.
Cypress even has a guide dedicated to this problem (docs.cypress.io/guides/tooling/typescript-support#Clashing-types-with-Jest).
Having the same import statements from @playwright/test
in every test file might be some boilerplate, but I think the reduced friction (by not having any globals) justifies that boilerplate.
Newer test runners like vitest
also do not set globals anymore.
Compared to Jest, there are some things missing from Playwright Test which are often needed when running unit tests.
Most of those things can be substituted by using libraries of the Node.js ecosystem.
Take a look at the example repository I made to see how to set up those things!
For Mocks & Spies use sinon
or jest-mock
(the mocking solution shipped with Jest).
For Fake timers use sinon
(See code changes).
Code coverage just needs a simple combination of the command-line interface nyc
of Istanbul (a code coverage tool for JavaScript) and source-map-support
(See code changes).
Watch mode is not supported but "planned work" at the time of writing: playwright.dev/docs/test-components#planned-work.
There are some workarounds in the related issue: github.com/microsoft/playwright/issues/7035.
Some test frameworks provide a way to mock CommonJS/ECMAScript modules.
Module mocking allows to replace the "thing" returned by require
/import
statements by some fake instance.
Unfortunately, this is not possible in Playwright. Although there are some Node.js libraries that allow module mocking (like testdouble
), they don't work because Playwright does some of the module loading itself (github.com/microsoft/playwright/issues/14398).
If you want to get module mocking support in Playwright, you can upvote this feature request: github.com/microsoft/playwright/issues/14572.
This is the biggest downside of using Playwright for unit tests in my opinion.
For now, if you want to test modules isolated from others you probably have to adapt the codebase to use some kind of dependency injection. Be it that dependencies are just passed as arguments when calling functions, or using a dependency injection solution like NestJS IoC containers (which I have used very successfully in the past).
Playwright Test has a method expect(...).toMatchSnapshot(...)
which can perform snapshot-comparisons on screenshots but also textual and binary data.
At the time of writing, Playwright will append a platform-specific suffix (like linux
) to the name of the file the snapshot is stored in. This makes sense for screenshots (since screenshots taken on different platforms will deviate slightly, for example because of text rendering), but it rarely makes sense in case of textual/binary data.
This is an open issue (github.com/microsoft/playwright/issues/11134). An easy workaround is to disable the suffix by setting up an "auto-fixture" and override testInfo.snapshotSuffix
:
import { test as base } from '@playwright/test';
export const test = base.extend<{ _autoSnapshotSuffix: void }>({
_autoSnapshotSuffix: [
async ({}, use, testInfo) => {
testInfo.snapshotSuffix = '';
await use();
},
{ auto: true },
],
});
#testing #playwright #test
1596754901
The shift towards microservices and modular applications makes testing more important and more challenging at the same time. You have to make sure that the microservices running in containers perform well and as intended, but you can no longer rely on conventional testing strategies to get the job done.
This is where new testing approaches are needed. Testing your microservices applications require the right approach, a suitable set of tools, and immense attention to details. This article will guide you through the process of testing your microservices and talk about the challenges you will have to overcome along the way. Let’s get started, shall we?
Traditionally, testing a monolith application meant configuring a test environment and setting up all of the application components in a way that matched the production environment. It took time to set up the testing environment, and there were a lot of complexities around the process.
Testing also requires the application to run in full. It is not possible to test monolith apps on a per-component basis, mainly because there is usually a base code that ties everything together, and the app is designed to run as a complete app to work properly.
Microservices running in containers offer one particular advantage: universal compatibility. You don’t have to match the testing environment with the deployment architecture exactly, and you can get away with testing individual components rather than the full app in some situations.
Of course, you will have to embrace the new cloud-native approach across the pipeline. Rather than creating critical dependencies between microservices, you need to treat each one as a semi-independent module.
The only monolith or centralized portion of the application is the database, but this too is an easy challenge to overcome. As long as you have a persistent database running on your test environment, you can perform tests at any time.
Keep in mind that there are additional things to focus on when testing microservices.
Test containers are the method of choice for many developers. Unlike monolith apps, which lets you use stubs and mocks for testing, microservices need to be tested in test containers. Many CI/CD pipelines actually integrate production microservices as part of the testing process.
As mentioned before, there are many ways to test microservices effectively, but the one approach that developers now use reliably is contract testing. Loosely coupled microservices can be tested in an effective and efficient way using contract testing, mainly because this testing approach focuses on contracts; in other words, it focuses on how components or microservices communicate with each other.
Syntax and semantics construct how components communicate with each other. By defining syntax and semantics in a standardized way and testing microservices based on their ability to generate the right message formats and meet behavioral expectations, you can rest assured knowing that the microservices will behave as intended when deployed.
It is easy to fall into the trap of making testing microservices complicated, but there are ways to avoid this problem. Testing microservices doesn’t have to be complicated at all when you have the right strategy in place.
There are several ways to test microservices too, including:
What’s important to note is the fact that these testing approaches allow for asynchronous testing. After all, asynchronous development is what makes developing microservices very appealing in the first place. By allowing for asynchronous testing, you can also make sure that components or microservices can be updated independently to one another.
#blog #microservices #testing #caylent #contract testing #end-to-end testing #hoverfly #integration testing #microservices #microservices architecture #pact #testing #unit testing #vagrant #vcr
1620983255
Automation and segregation can help you build better software
If you write automated tests and deliver them to the customer, he can make sure the software is working properly. And, at the end of the day, he paid for it.
Ok. We can segregate or separate the tests according to some criteria. For example, “white box” tests are used to measure the internal quality of the software, in addition to the expected results. They are very useful to know the percentage of lines of code executed, the cyclomatic complexity and several other software metrics. Unit tests are white box tests.
#testing #software testing #regression tests #unit tests #integration tests