Playwright configuration: POM fixtures for projects - playwright

I need to configure Playwright to use different POM fixtures for different project settings.
All examples I find configure the POM while extending the base test. This works, but this way Playwright would use the same POM fixture for all projects.
import { test as base } from '#playwright/test';
type TestOptions = {
productDetailPom: ProductDetailPom
};
export const test = base.extend<TestOptions>({
productDetailPom: async ({ browser }, use) => {
await use(await ProductDetailPom.create(browser, 'url'));
},
});
What I need are different POMs for each configured project. Is there a way to create a POM instance with the browser or page fixture for each project in the config?
// playwright.config.ts
const config: PlaywrightTestConfig<TestOptions> = {
...
projects: [
{
name: 'proj1',
use: {
productDetailPom: new ProductDetailPom1(browser, 'url1') // POM instance 1
}
},
{
name: 'proj2',
use: {
productDetailPom: new ProductDetailPom2(browser, 'url2') // POM instance 2
}
}
],
};

I found no way to construct POMs in the config, but a workaround. Here is what I came up with. Please tell me if you have a better solution.
In short: The POM instances are created not in the config, but later. For instantiation I use a factory, which can be the same for all projects. Only the data fed into the factory differ per project.
In the config I add the data I need for construction: the POM class itself and data that will get passed to the constructor. Those data can be different in each project.
// playwright.config.ts
const config: PlaywrightTestConfig<TestOptions> = {
...
projects: [
{
name: 'proj1',
use: {
productDetailPomConstr: [ProductDetailPom1, 'url1']
},
},
{
name: 'proj2',
use: {
productDetailPomConstr: [ProductDetailPom2, 'url2']
}
}
]
};
For creating the instance of the POM I use a factory, which is the same for all projects and because of this can be added by extending the base test. This is where I get the browser instance from (you could also get all other fixtures like page).
import { test as base } from '#playwright/test';
type TestOptions = {
pomFactory: PomFactory,
productDetailPomConstr: [typeof ProductDetailPom, Record<string, any>]
};
export const test = base.extend<TestOptions>({
pomFactory: async ({ browser }, use) => {
const pomFactory = new PomFactory(browser);
await use(pomFactory);
},
productDetailPomConstr: [null, { option: true }],
});
In the test I can get both fixtures, factory and PomConstruction data, and instantiate my POM with them.
test('someTest', async ({pomFactory, productDetailPomConstr}) => {
const pdPom = await pomFactory.create<ProductDetailPom>(productDetailPomConstr);
});
Maybe this helps someone.

Related

Playwright save storage state only for certain files

I currently have a test pack that has >50 files. 49 of the files will use the same authentication, so I have set up the below inside my playwright config file:
storageState: 'myauth.json',
This allows me to store the state and use it across all of the tests,
the issue becomes where I don't want to use this state in one of my tests. How would one go about this?
I'm aware I could pass the state into 49 of the files and ignore it for the one, but this seems like the wrong idea.
If you do not want to use any storageState in your 50th test, then you can use test.use({ storageState: undefined }); in that test:
import { test } from '#playwright/test';
import LoginPage from '../../page/LoginPage';
test.use({ storageState: undefined });
test('This test ignores the storageState and performs a fresh login', async ({ page }) => {
loginPage = new LoginPage(page);
await loginPage.login();
});
You can use fixtures and as default value set the one you use 49 times.
In that case when you are need other auth just overwrite it with keyword "use". It is described in playwright doc. https://playwright.dev/docs/test-fixtures
import { TodoPage } from './todo-page'; //In your case auth1 auth2
import { SettingsPage } from './settings-page';
// Declare the types of your fixtures.
type MyFixtures = {
todoPage: TodoPage;
settingsPage: SettingsPage;
};
// 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.
export const test = base.extend<MyFixtures>({
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));
},
});
Default auth use as you you page in your tests.
And when you need to overwrite in your specific file spec.ts file just write:
test.use({ auth: 'reareAuth' });

Use joi schema in swagger

At node.js, I just started working with swagger, and I want to integrate the parameter-validation schemas I created using 'joi' - with swagger.
There's https://www.npmjs.com/package/joi-to-swagger but I don't understand how to use the generated object in swagger...
This is how I use swagger:
swaggerAutogen(outputFile, endpointsFiles, doc);
This is how I use joi-to-swagger:
const { swagger, components } = j2s(postVersionInfoValidator);
How do I integrate the swagger-output.json created in the first step, with the swagger object created in the 2nd step?
Thanks
You can do something like this
Generate the Swagger schema using joi-to-swagger
import j2s from 'joi-to-swagger';
const { swagger: swaggerA } = j2s(joiSchemaA);
const { swagger: swaggerB } = j2s(joiSchemaB);
const { swagger: swaggerC } = j2s(joiSchemaC);
Add the generated schemas into the swagger-autogen options. Note the usage of #definitions instead of definition. This is to allow us to use the Swagger schema object generated from joi-to-swagger as is.
import swaggerAutogen from 'swagger-autogen';
const outputFile = './swagger-output.json';
const apiFiles = ['./app.js'];
const doc = {
info: {
title: 'Your API',
...
},
'#definitions': {
PayloadA: swaggerA,
PayloadB: swaggerB,
PayloadC: swaggerC,
},
...
};
swaggerAutogen({ openapi: '3.0.0' })(outputFile, apiFiles, doc);
Add the Swagger comments into your endpoints
app.post('/endpoint-a', (res, req) => {
// #swagger.summary = 'Perform stuff'
/* #swagger.requestBody = {
required: true,
schema: { $ref: "#/definitions/PayloadA" },
}
*/
res.send('A');
});

aws cdk lambda, appconfig typescript example please?

Can anyone provide or point at an AWS-CDK/typescript example provisioning an AWS AppConfig app/env/config consumed by a lambda please? Could not find anything meaningful online.
A little late to this party, If anyone is coming to this question, looking for a quick answer, here you go:
AppConfig Does Not Have Any Official L2 Constructs
This means that one has to work with L1 constructs currently vended here
This is ripe for someone authoring an L2/L3 construct (I am working on vending this custom construct soon - so keep an eye out on updates here).
This does not mean its hard to use AppConfig in CDK, its just that unlike L2/L3 constructs, we have to dig deeper into setting AppConfig up reading their documentation.
A Very Simple Example (YMMV)
Here is a custom construct I have to setup AppConfig:
import {
CfnApplication,
CfnConfigurationProfile,
CfnDeployment,
CfnDeploymentStrategy,
CfnEnvironment,
CfnHostedConfigurationVersion,
} from "aws-cdk-lib/aws-appconfig";
import { Construct } from "constructs";
// 1. https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_appconfig-readme.html - there are no L2 constructs for AppConfig.
// 2. https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-working.html- this the CDK code from this console setup guide.
export class AppConfig extends Construct {
constructor(scope: Construct, id: string) {
super(scope, id);
// create a new app config application.
const testAppConfigApp: CfnApplication = new CfnApplication(
this,
"TestAppConfigApp",
{
name: "TestAppConfigApp",
}
);
// you can customize this as per your needs.
const immediateDeploymentStrategy = new CfnDeploymentStrategy(
this,
"DeployStrategy",
{
name: "ImmediateDeployment",
deploymentDurationInMinutes: 0,
growthFactor: 100,
replicateTo: "NONE",
finalBakeTimeInMinutes: 0,
}
);
// setup an app config env
const appConfigEnv: CfnEnvironment = new CfnEnvironment(
this,
"AppConfigEnv",
{
applicationId: testAppConfigApp.ref,
// can be anything that makes sense for your use case.
name: "Production",
}
);
// setup config profile
const appConfigProfile: CfnConfigurationProfile = new CfnConfigurationProfile(
this,
"ConfigurationProfile",
{
name: "TestAppConfigProfile",
applicationId: testAppConfigApp.ref,
// we want AppConfig to manage the configuration profile, unless we need from SSM or S3.
locationUri: "hosted",
// This can also be "AWS.AppConfig.FeatureFlags"
type: "AWS.Freeform",
}
);
// Update AppConfig
const configVersion: CfnHostedConfigurationVersion = new CfnHostedConfigurationVersion(
this,
"HostedConfigurationVersion",
{
applicationId: testAppConfigApp.ref,
configurationProfileId: appConfigProfile.ref,
content: JSON.stringify({
someAppConfigResource: "SomeAppConfigResourceValue",
//... add more as needed.
}),
// https://www.rfc-editor.org/rfc/rfc9110.html#name-content-type
contentType: "application/json",
}
);
// Perform deployment.
new CfnDeployment(this, "Deployment", {
applicationId: testAppConfigApp.ref,
configurationProfileId: appConfigProfile.ref,
configurationVersion: configVersion.ref,
deploymentStrategyId: immediateDeploymentStrategy.ref,
environmentId: appConfigEnv.ref,
});
}
}
Here is what goes inside my lambda handler (please note that you should have lambda layers enabled for AppConfig extension, see more information here):
const http = require('http');
exports.handler = async (event) => {
const res = await new Promise((resolve, reject) => {
http.get(
"http://localhost:2772/applications/TestAppConfigApp/environments/Production/configurations/TestAppConfigProfile",
resolve
);
});
let configData = await new Promise((resolve, reject) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('error', err => reject(err));
res.on('end', () => resolve(data));
});
const parsedConfigData = JSON.parse(configData);
return parsedConfigData;
};
EDIT: As promised, I released a new npm library : https://www.npmjs.com/package/cdk-appconfig, as of this update, this is still pre-release, but time permitting I will release a 1.x soon. But this will allow people to check out/fork if need be. Please share feedback/or open PRs if you'd like to collaborate.
I have no experience with AWS AppConfig. But I would start here and here

How to create multiple output paths in Webpack config

Does anyone know how to create multiple output paths in a webpack.config.js file? I'm using bootstrap-sass which comes with a few different font files, etc. For webpack to process these i've included file-loader which is working correctly, however the files it outputs are being saved to the output path i specified for the rest of my files:
output: {
path: __dirname + "/js",
filename: "scripts.min.js"
}
I'd like to achieve something where I can maybe look at the extension types for whatever webpack is outputting and for things ending in .woff .eot, etc, have them diverted to a different output path. Is this possible?
I did a little googling and came across this *issue on github where a couple of solutions are offered, edit:
but it looks as if you need to know the entry point in able to specify an output using the hash method
eg:
var entryPointsPathPrefix = './src/javascripts/pages';
var WebpackConfig = {
entry : {
a: entryPointsPathPrefix + '/a.jsx',
b: entryPointsPathPrefix + '/b.jsx',
c: entryPointsPathPrefix + '/c.jsx',
d: entryPointsPathPrefix + '/d.jsx'
},
// send to distribution
output: {
path: './dist/js',
filename: '[name].js'
}
}
*https://github.com/webpack/webpack/issues/1189
however in my case, as far as the font files are concerned, the input process is kind of abstracted away and all i know is the output. in the case of my other files undergoing transformations, there's a known point where i'm requiring them in to be then handled by my loaders. if there was a way of finding out where this step was happening, i could then use the hash method to customize output paths, but i don't know where these files are being required in.
Webpack does support multiple output paths.
Set the output paths as the entry key. And use the name as output template.
webpack config:
entry: {
'module/a/index': 'module/a/index.js',
'module/b/index': 'module/b/index.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
generated:
└── module
├── a
│   └── index.js
└── b
└── index.js
I'm not sure if we have the same problem since webpack only support one output per configuration as of Jun 2016. I guess you already seen the issue on Github.
But I separate the output path by using the multi-compiler. (i.e. separating the configuration object of webpack.config.js).
var config = {
// TODO: Add common Configuration
module: {},
};
var fooConfig = Object.assign({}, config, {
name: "a",
entry: "./a/app",
output: {
path: "./a",
filename: "bundle.js"
},
});
var barConfig = Object.assign({}, config,{
name: "b",
entry: "./b/app",
output: {
path: "./b",
filename: "bundle.js"
},
});
// Return Array of Configurations
module.exports = [
fooConfig, barConfig,
];
If you have common configuration among them, you could use the extend library or Object.assign in ES6 or {...} spread operator in ES7.
You can now (as of Webpack v5.0.0) specify a unique output path for each entry using the new "descriptor" syntax (https://webpack.js.org/configuration/entry-context/#entry-descriptor) –
module.exports = {
entry: {
home: { import: './home.js', filename: 'unique/path/1/[name][ext]' },
about: { import: './about.js', filename: 'unique/path/2/[name][ext]' }
}
};
If you can live with multiple output paths having the same level of depth and folder structure there is a way to do this in webpack 2 (have yet to test with webpack 1.x)
Basically you don't follow the doc rules and you provide a path for the filename.
module.exports = {
entry: {
foo: 'foo.js',
bar: 'bar.js'
},
output: {
path: path.join(__dirname, 'components'),
filename: '[name]/dist/[name].bundle.js', // Hacky way to force webpack to have multiple output folders vs multiple files per one path
}
};
That will take this folder structure
/-
foo.js
bar.js
And turn it into
/-
foo.js
bar.js
components/foo/dist/foo.js
components/bar/dist/bar.js
Please don't use any workaround because it will impact build performance.
Webpack File Manager Plugin
Easy to install copy this tag on top of the webpack.config.js
const FileManagerPlugin = require('filemanager-webpack-plugin');
Install
npm install filemanager-webpack-plugin --save-dev
Add the plugin
module.exports = {
plugins: [
new FileManagerPlugin({
onEnd: {
copy: [
{source: 'www', destination: './vinod test 1/'},
{source: 'www', destination: './vinod testing 2/'},
{source: 'www', destination: './vinod testing 3/'},
],
},
}),
],
};
Screenshot
If it's not obvious after all the answers you can also output to a completely different directories (for example a directory outside your standard dist folder). You can do that by using your root as a path (because you only have one path) and by moving the full "directory part" of your path to the entry option (because you can have multiple entries):
entry: {
'dist/main': './src/index.js',
'docs/main': './src/index.js'
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, './'),
}
This config results in the ./dist/main.js and ./docs/main.js being created.
In my case I had this scenario
const config = {
entry: {
moduleA: './modules/moduleA/index.js',
moduleB: './modules/moduleB/index.js',
moduleC: './modules/moduleB/v1/index.js',
moduleC: './modules/moduleB/v2/index.js',
},
}
And I solve it like this (webpack4)
const config = {
entry: {
moduleA: './modules/moduleA/index.js',
moduleB: './modules/moduleB/index.js',
'moduleC/v1/moduleC': './modules/moduleB/v1/index.js',
'moduleC/v2/MoculeC': './modules/moduleB/v2/index.js',
},
}
You definitely can return array of configurations from your webpack.config file. But it's not an optimal solution if you just want a copy of artifacts to be in the folder of your project's documentation, since it makes webpack build your code twice doubling the overall time to build.
In this case I'd recommend to use the FileManagerWebpackPlugin plugin instead:
const FileManagerPlugin = require('filemanager-webpack-plugin');
// ...
plugins: [
// ...
new FileManagerPlugin({
onEnd: {
copy: [{
source: './dist/*.*',
destination: './public/',
}],
},
}),
],
You can only have one output path.
from the docs https://github.com/webpack/docs/wiki/configuration#output
Options affecting the output of the compilation. output options tell Webpack how to write the compiled files to disk. Note, that while there can be multiple entry points, only one output configuration is specified.
If you use any hashing ([hash] or [chunkhash]) make sure to have a consistent ordering of modules. Use the OccurenceOrderPlugin or recordsPath.
I wrote a plugin that can hopefully do what you want, you can specify known or unknown entry points (using glob) and specify exact outputs or dynamically generate them using the entry file path and name. https://www.npmjs.com/package/webpack-entry-plus
I actually wound up just going into index.js in the file-loader module and changing where the contents were emitted to. This is probably not the optimal solution, but until there's some other way, this is fine since I know exactly what's being handled by this loader, which is just fonts.
//index.js
var loaderUtils = require("loader-utils");
module.exports = function(content) {
this.cacheable && this.cacheable();
if(!this.emitFile) throw new Error("emitFile is required from module system");
var query = loaderUtils.parseQuery(this.query);
var url = loaderUtils.interpolateName(this, query.name || "[hash].[ext]", {
context: query.context || this.options.context,
content: content,
regExp: query.regExp
});
this.emitFile("fonts/"+ url, content);//changed path to emit contents to "fonts" folder rather than project root
return "module.exports = __webpack_public_path__ + " + JSON.stringify( url) + ";";
}
module.exports.raw = true;
u can do lik
var config = {
// TODO: Add common Configuration
module: {},
};
var x= Object.assign({}, config, {
name: "x",
entry: "./public/x/js/x.js",
output: {
path: __dirname+"/public/x/jsbuild",
filename: "xbundle.js"
},
});
var y= Object.assign({}, config, {
name: "y",
entry: "./public/y/js/FBRscript.js",
output: {
path: __dirname+"/public/fbr/jsbuild",
filename: "ybundle.js"
},
});
let list=[x,y];
for(item of list){
module.exports =item;
}
The problem is already in the language:
entry (which is a object (key/value) and is used to define the inputs*)
output (which is a object (key/value) and is used to define outputs*)
The idea to differentiate the output based on limited placeholder like '[name]' defines limitations.
I like the core functionality of webpack, but the usage requires a rewrite with abstract definitions which are based on logic and simplicity... the hardest thing in software-development... logic and simplicity.
All this could be solved by just providing a list of input/output definitions... A LIST INPUT/OUTPUT DEFINITIONS.
Vinod Kumar's good workaround is:
module.exports = {
plugins: [
new FileManagerPlugin({
events: {
onEnd: {
copy: [
{source: 'www', destination: './vinod test 1/'},
{source: 'www', destination: './vinod testing 2/'},
{source: 'www', destination: './vinod testing 3/'},
],
},
}
}),
],
};

How can one split API documentation in multiple files using Swagger 2.0

According to Swagger 2.0 specs, it might be possible to do this. I am referencing PathObject using $ref which points to another file. We used to be able to do this nicely using Swagger 1.2. But Swagger-UI does not seem to be able to read the referred PathObject in another file.
Is this part of spec too new and is not yet supported? Is there a way to split each "path"'s documentation into another file?
{
"swagger": "2.0",
"basePath": "/rest/json",
"schemes": [
"http",
"https"
],
"info": {
"title": "REST APIs",
"description": "desc",
"version": "1.0"
},
"paths": {
"/time": {
"$ref": "anotherfile.json"
}
}
}
To support multiple files, your libraries have to support dereferencing the $ref field. But I would not recommend to deliver the swagger file with unresolved references. Our swagger defintion has around 30-40 files. Delivering them via HTTP/1.1 could slow down any reading application.
Since we are building javascript libs, too, we already had a nodejs based build system using gulp. For the node package manager (npm) you can find some libraries supporting dereferencing to build one big swagger file.
Our base file looks like this (shortened):
swagger: '2.0'
info:
version: 2.0.0
title: App
description: Example
basePath: /api/2
paths:
$ref: "routes.json"
definitions:
example:
$ref: "schema/example.json"
The routes.json is generated from our routing file. For this we use a gulp target implementing swagger-jsdoc like this:
var gulp = require('gulp');
var fs = require('fs');
var gutil = require('gulp-util');
var swaggerJSDoc = require('swagger-jsdoc');
gulp.task('routes-swagger', [], function (done) {
var options = {
swaggerDefinition: {
info: {
title: 'Routes only, do not use, only for reference',
version: '1.0.0',
},
},
apis: ['./routing.php'], // Path to the API docs
};
var swaggerSpec = swaggerJSDoc(options);
fs.writeFile('public/doc/routes.json', JSON.stringify(swaggerSpec.paths, null, "\t"), function (error) {
if (error) {
gutil.log(gutil.colors.red(error));
} else {
gutil.log(gutil.colors.green("Succesfully generated routes include."));
done();
}
});
});
And for generating the swagger file, we use a build task implementing SwaggerParser like this:
var gulp = require('gulp');
var bootprint = require('bootprint');
var bootprintSwagger = require('bootprint-swagger');
var SwaggerParser = require('swagger-parser');
var gutil = require('gulp-util');
var fs = require('fs');
gulp.task('swagger', ['routes-swagger'], function () {
SwaggerParser.bundle('public/doc/swagger.yaml', {
"cache": {
"fs": false
}
})
.then(function(api) {
fs.writeFile('public/doc/swagger.json', JSON.stringify(api, null, "\t"), function (error) {
if (error) {
gutil.log(gutil.colors.red(error));
} else {
gutil.log("Bundled API %s, Version: %s", gutil.colors.magenta(api.info.title), api.info.version);
}
});
})
.catch(function(err) {
gutil.log(gutil.colors.red.bold(err));
});
});
With this implementation we can maintain a rather large swagger specification and we are not restricted to special programming language or framework implementation, since we define the paths in the comments to the real routing definitions. (Note: The gulp tasks are split in multiple files too.)
While it would theoretically be possible to do that in the future, the solution is still not fully baked into the supporting tools so for now I'd highly recommend keeping it in one file.
If you're looking for a way to manage and navigate the Swagger definition, I'd recommend using the YAML format of the spec, where you can add comments and that may ease up navigation and splitting of a large definition.
You can also use JSON Refs library to resolve such multi-file Swagger spec.
I've written about it in this blog post
There is also this GitHub repo to demonstrate how all of this work.
My solution to this problem is using this package below to solve the reference issue
https://www.npmjs.com/package/json-schema-ref-parser
Here is the code snippet when generating the swagger UI using that library. I was using Express.js for my node server.
import express from 'express';
import * as path from 'path';
import refParser from '#apidevtools/json-schema-ref-parser';
import swaggerUi from 'swagger-ui-express';
const port = 3100;
const app = express();
app.get('/', async (req, res) => {
res.redirect('/api-docs')
});
app.use(
'/api-docs',
async function (req: express.Request, res: express.Response, next: express.NextFunction) {
const schemaFilePath = path.join(__dirname, 'schema', 'openapi.yml');
try {
// Resolve $ref in schema
const swaggerDocument = await refParser.dereference(schemaFilePath);
(req as any).swaggerDoc = swaggerDocument;
next();
} catch (err) {
console.error(err);
next(err);
}
},
swaggerUi.serve,
swaggerUi.setup()
);
app.listen(port, () => console.log(`Local web server listening on port ${port}!`));
Take a look at my Github repository to see how it works

Resources