In my Playwright tests, I set the base-url according to the docs:
const config: PlaywrightTestConfig = {
projects: [
{
name: 'Safari MacBook Air',
use: {
browserName: 'webkit',
viewport: {
width: 2560,
height: 1620,
},
contextOptions: {
ignoreHTTPSErrors: true,
},
},
},
],
use: {
baseURL: process.env.PLATFORMSH_URL,
headless: false,
locale: 'ja-JP',
// Debugging artifacts.
screenshot: 'on',
trace: 'on',
video: 'on',
},
};
export default config;
This is working for goto:
await this.page.goto('/myDirectory');
However, it fails for expect:
expect(page.url()).toBe('/myPage');
The error is:
Expected: "/myPage"
Received: "https://www.example.com/myPage"
How can I use expect with baseURL?
Try using this assertion instead:
For example, having configured Playwright homepage as our baseUrl
{
name: 'webkit',
use: {
...devices['Desktop Safari'],
baseURL: 'https://playwright.dev/'
},
},
Then:
test('baseUrl', async ({ page }) => {
await page.goto('/docs/intro');
await expect(page).toHaveURL('/docs/intro');
});
If you'd like to continue using this format:
expect(page.url()).toBe('/myPage');
Then you need to change the assertion, because the url you're at is not equal to your directory. You can assert the url you're at contains the aforementioned directory, instead:
expect(page.url()).toContain('/myPage');
There is nothing wrong in your code except the last line.
page.url() will give you the whole URL address on wich your
driver (browser,whathever) currently is, and expect(something)toBe(thing) is like equals, and it will fail in your case.
You can assert in several ways like:
await expect(page.url().includes(partOfUrl)).toBeTruthy();
In the end I just wrapped it in my own utility function:
const pwaBaseUrl = process.env.NOSLASH_URL;
export const assertPath = async (page: Page, path: string) => {
expect(page.url()).toBe(`${pwaBaseUrl}/${path}`);
};
This guarantees that I am on the exact path. Perhaps this isn't generally necessary, but coming from Behat testing on PHP, this is what I'm used to.
Related
I'm developing a little CDKv2 script to instantiate a few AWS services.
I have some lambda code deployed in the lambda/ folder and the frontend stored in a bucket populated using the frontend/ folder in the source.
I've noticed that whenever I make a change to any of the file inside these two, cdk watch return the following error and falls back to perform a full redeploy (which is significantly slow).
Could not perform a hotswap deployment, because the CloudFormation template could not be resolved: Parameter or resource 'DomainPrefix' could not be found for evaluation
Falling back to doing a full deployment
Is there any way to make changes in these folders only trigger updating the related bucket content or the related lambda?
Following here the stack.ts for quick reference, just in case here you can take a look at the repo.
export class CdkAuthWebappStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
const domainPrefixParam = new CfnParameter(this, 'DomainPrefix', {
type: 'String',
description: 'You have to set it in google cloud as well', //(TODO: add link to explain properly)
default: process.env.DOMAIN_NAME || ''
})
const googleClientIdParam = new CfnParameter(this, 'GoogleClientId', {
type: 'String',
description: 'From google project',
noEcho: true,
default: process.env.GOOGLE_CLIENT_ID || ''
})
const googleClientSecretParam = new CfnParameter(this, 'GoogleClientSecret', {
type: 'String',
description: 'From google project',
noEcho: true,
default: process.env.GOOGLE_CLIENT_SECRET || ''
})
if(!domainPrefixParam.value || !googleClientIdParam.value || !googleClientSecretParam.value){
throw new Error('Make sure you initialized DomainPrefix, GoogleClientId and GoogleClientSecret in the stack parameters')
}
const s3frontend = new s3.Bucket(this, 'Bucket', {
bucketName: domainPrefixParam.valueAsString+'-frontend-bucket',
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
encryption: s3.BucketEncryption.S3_MANAGED,
enforceSSL: true,
versioned: false,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
websiteIndexDocument: "index.html",
});
//TODO: fare in modo che questa origin access identity non sia legacy quando deployo
const cfdistributionoriginaccessidentity = new cloudfront.OriginAccessIdentity(this, 'CFOriginAccessIdentity', {
comment: "Used to give bucket read to cloudfront"
})
const cfdistribution = new cloudfront.CloudFrontWebDistribution(this, 'CFDistributionFrontend', {
originConfigs: [
{
s3OriginSource: {
s3BucketSource: s3frontend,
originAccessIdentity: cfdistributionoriginaccessidentity
},
behaviors: [{
isDefaultBehavior: true,
allowedMethods: cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
forwardedValues: {
queryString: true,
cookies: { forward: 'all' }
},
minTtl: cdk.Duration.seconds(0),
defaultTtl: cdk.Duration.seconds(3600),
maxTtl: cdk.Duration.seconds(86400)
}]
}
]
})
s3frontend.grantRead(cfdistributionoriginaccessidentity)
const cfdistributionpolicy = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['cloudfront:CreateInvalidation'],
resources: [`"arn:aws:cloudfront::${this.account}:distribution/${cfdistribution.distributionId}"`]
});
const userpool = new cognito.UserPool(this, 'WebAppUserPool', {
userPoolName: 'web-app-user-pool',
selfSignUpEnabled: false
})
const userpoolidentityprovidergoogle = new cognito.UserPoolIdentityProviderGoogle(this, 'WebAppUserPoolIdentityGoogle', {
clientId: googleClientIdParam.valueAsString,
clientSecret: googleClientSecretParam.valueAsString,
userPool: userpool,
attributeMapping: {
email: cognito.ProviderAttribute.GOOGLE_EMAIL
},
scopes: [ 'email' ]
})
// this is used to make the hostedui reachable
userpool.addDomain('Domain', {
cognitoDomain: {
domainPrefix: domainPrefixParam.valueAsString
}
})
const CLOUDFRONT_PUBLIC_URL = `https://${cfdistribution.distributionDomainName}/`
const client = userpool.addClient('Client', {
oAuth: {
flows: {
authorizationCodeGrant: true
},
callbackUrls: [
CLOUDFRONT_PUBLIC_URL
],
logoutUrls: [
CLOUDFRONT_PUBLIC_URL
],
scopes: [
cognito.OAuthScope.EMAIL,
cognito.OAuthScope.OPENID,
cognito.OAuthScope.PHONE
]
},
supportedIdentityProviders: [
cognito.UserPoolClientIdentityProvider.GOOGLE
]
})
client.node.addDependency(userpoolidentityprovidergoogle)
// defines an AWS Lambda resource
const securedlambda = new lambda.Function(this, 'AuhtorizedRequestsHandler', {
runtime: lambda.Runtime.NODEJS_14_X,
code: lambda.Code.fromAsset('lambda'),
handler: 'secured.handler'
});
const lambdaapiintegration = new apigw.LambdaIntegration(securedlambda)
const backendapigw = new apigw.RestApi(this, 'AuthorizedRequestAPI', {
restApiName: domainPrefixParam.valueAsString,
defaultCorsPreflightOptions: {
"allowOrigins": apigw.Cors.ALL_ORIGINS,
"allowMethods": apigw.Cors.ALL_METHODS,
}
})
const backendapiauthorizer = new apigw.CognitoUserPoolsAuthorizer(this, 'BackendAPIAuthorizer', {
cognitoUserPools: [userpool]
})
const authorizedresource = backendapigw.root.addMethod('GET', lambdaapiintegration, {
authorizer: backendapiauthorizer,
authorizationType: apigw.AuthorizationType.COGNITO
})
const s3deploymentfrontend = new s3deployment.BucketDeployment(this, 'DeployFrontEnd', {
sources: [
s3deployment.Source.asset('./frontend'),
s3deployment.Source.data('constants.js', `const constants = {domainPrefix:'${domainPrefixParam.valueAsString}', region:'${this.region}', cognito_client_id:'${client.userPoolClientId}', apigw_id:'${backendapigw.restApiId}'}`)
],
destinationBucket: s3frontend,
distribution: cfdistribution
})
new cdk.CfnOutput(this, 'YourPublicCloudFrontURL', {
value: CLOUDFRONT_PUBLIC_URL,
description: 'Navigate to the URL to access your deployed application'
})
}
}
Recording the solution from the comments:
Cause:
cdk watch apparently does not work with template parameters. I guess this is because the default --hotswap option bypasses CloudFormation and deploys instead via SDK commands.
Solution:
Remove the CfnParamters from the template. CDK recommends not using parameters in any case.
Perhaps cdk watch --no-hotswap would also work?
Here is how I have my playwright config file:
import { PlaywrightTestConfig } from '#playwright/test';
const config: PlaywrightTestConfig = {
reporter: [
['list'],
],
timeout: 30000,
projects: [
{
testDir: `${process.cwd()}/qe/specs/e2e/`,
testMatch: '*_spec.ts',
name: 'Chrome Local',
use: {
viewport: { width: 900, height: 700 },
launchOptions: {
channel: 'chrome',
},
headless: false,
},
},
]}
Here is my spec file:
import { test } from '#playwright/test';
const BASE_URL = process.env.URL || 'http://www.google.com';
let page: any;
test.describe('Main Block Test', () => {
test.beforeAll(async ({ browser }) => {
page = await browser.newPage();
});
test('Testing block 1', async ({ page }) => {
await page.goto(BASE_URL);
await page.waitForTimeout(2000);
});
test('Testing block 2', async ({ page }) => {
await page.goto('www.google.com);
await page.waitForTimeout(2000);
});
});
This kinda works but the viewport set in config file is not respected.
I know if declare page in every test block, then it will be, but there has to be a better way to do this and be able to declare page in beforeAll and use it throught? Any help will be great. Thank you!
I'm writing a test to register users on my site. Using #playwright/test, I have defined several different projects in playwright.config.ts:
{
name: 'iPhone 12 Pro',
use: devices['iPhone 12 Pro'],
},
{
name: 'iPhone 12 Pro Max',
use: devices['iPhone 12 Pro Max'],
},
{
name: 'iPhone 5/SE',
use: devices['iPhone SE'],
},
My test looks like this:
test('Register a user', async ({ page }) => {
// Go to baseUrl/webapp/
await page.goto(`${baseUrl}webapp/`);
// Click the register tab.
await page.click('ion-segment-button[role="tab"]:has-text("Register")');
await page.click('input[name="mail"]');
// #TODO: How do I get the project name here?
await page.fill('input[name="mail"]', `test#example.com`);
// Press Tab
await page.press('input[name="mail"]', 'Tab');
await page.fill('input[name="pass"]', 'password');
However, when I run this test, it only works for the first worker because you can only register an email address once.
So what I would like to do is to get access to the project name (for example, iPhone 12 Pro) in my test so that I can convert that to an email address so that each time the test is run, it will register a user based on the project name.
How can I get the project name within a playwright test?
I read the Playwright documentation about the workerInfo object but I can't figure out how to apply it within the test.
You have access to the workerInfo like that as a second parameter, which you can use in that case to make your email unique per worker:
test('Register a user', async ({ page }, workerInfo) => {
// Go to baseUrl/webapp/
await page.goto(`${baseUrl}webapp/`);
// Click the register tab.
await page.click('ion-segment-button[role="tab"]:has-text("Register")');
await page.click('input[name="mail"]');
await page.fill('input[name="mail"]', `test-user-${workerInfo.workerIndex}#example.com`);
You can also get the project name by workerInfo.project.name.
See here for more information.
Nice option is to access testInfo:
test('config projects name', async ({}, testInfo) => {
const projectsName = testInfo.project.name;
expect(projectsName).toEqual('chromium');
});
Tested for config:
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
}
],
Docs: https://playwright.dev/docs/api/class-testinfo
Hint: In VSC during debugging you can quickly find how this object looks like
Hint2: Copy value of object testInfo from debugger and paste in new js file. Then find best parameter what suite you best (example below in Notepad++)
you can put custom project configuration -
define your custom type TestOptions:
//testOptions.ts
import { test as base } from '#playwright/test';
export type TestOptions = {
person: string;
pageName: string;
weight: number;
};
export const test = base.extend<TestOptions>({
// change later in the playwright.config.ts
person: ['AAA', { option: true }],
pageName: ['nothing', { option: true }],
weight: [0, { option: true }],
});
in playwright.config.ts add actual custom properties for the project:
import { TestOptions } from './runner/testOptions';
...
projects: [
{
name: 'demo1',
use: { person: 'Alice', pageName: 'open1', weight: 150 },
},
{
name: 'demo2',
use: { person: 'Alex', pageName: 'open2', weight: 180 },
},
...
access specific properties as
test('test project properties', async ({ page }, testInfo) => {
console.log(`--- calling project: ${testInfo.project.name}`)
console.log(`--- project.person: ${testInfo.project.use["person"]}`)
console.log(`--- project.pageName: ${testInfo.project.use["pageName"]}`)
console.log(`--- project.weight: ${testInfo.project.use["weight"]}`)
});
call actual test for given project demo1
npx playwright test -g "test project properties" --project=demo1
output:
--- calling project: demo1
--- project.person: Alice
--- project.pageName: open1
--- project.weight: 150
i have a problem with hashes in vue cli build.
And now to the details.
I build my app, and in dist folder i see my files with names like
app.dsadas.js, chunk-1-dsadaas.js etc.. all looks good.
But i build my app in docker images, there may be 2 or more, and i need all this images with the same hashes in filenames, but it is not.
This 'webpack-md5-hash' plugin helped me with this problem, but its very old solution, works with warnings.
Help pls find solution for webpack 4.
This is my vue config file:
const path = require('path');
const VuetifyLoaderPlugin = require('vuetify-loader/lib/plugin');
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const productionGzipExtensions = ['js', 'css'];
function resolve(dir) {
return path.resolve(__dirname, dir);
}
module.exports = {
assetsDir: 'static',
runtimeCompiler: true,
lintOnSave: process.env.NODE_ENV !== 'production',
devServer: {
overlay: {
warnings: true,
errors: true,
},
},
configureWebpack: {
performance: {
hints: false,
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
vue$: 'vue/dist/vue.esm.js',
'#': resolve('src'),
utils: resolve('src/utils'),
api: resolve('src/api'),
defaultStates: resolve('src/store/modules/defaultStates'),
router: resolve('src/router'),
store: resolve('src/store'),
config: resolve('src/config'),
helpers: resolve('src/store/modules/helpers'),
constants: resolve('src/constants'),
mixins: resolve('src/mixins'),
},
},
plugins: [
new VuetifyLoaderPlugin(),
new CompressionWebpackPlugin({
filename: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(`\\.(${productionGzipExtensions.join('|')})$`),
threshold: 10240,
minRatio: 0.8,
}),
],
},
};
I'm pulling my hair out on this one...
I have a view with some grids, a store and a viewModel. I need different filtered versions of the store in different grids, so I'm trying to bind each filtered store to a grid. Now I can't even get a store to load in a grid in the first place...
Here's what my code looks like:
Store:
Ext.define('My.store.Admin.Kinder', {
extend: 'Ext.data.Store',
model: 'My.model.Kind',
storeId: 'adminKinderStore',
alias: 'store.adminKinder',
proxy: {
type: 'ajax',
method: 'post',
url: '/getKinder',
reader: {
type: 'json',
rootProperty: 'kinder'
}
}
});
ViewModel:
Ext.define('My.model.kindViewModel', {
extend: 'Ext.app.ViewModel',
alias: 'viewmodel.kindViewModel',
requires: [
'My.model.Kind',
'My.store.Admin.Kinder'
],
view: 'kindView',
stores: {
warteliste: {
type: 'adminKinder'
}
}
});
View:
Ext.define('My.view.Admin.kinder', {
extend: 'Ext.panel.Panel',
alias: 'widget.kindView',
id: 'kinder-panel',
requires: [
'My.view.Admin.kindController',
'My.model.kindViewModel'
],
controller: 'kind',
border: false,
maxWidth: 960,
session: My.session,
viewModel: {
type: 'kindViewModel'
},
initComponent: function() {
this.activeTab = 'warteliste-tab';
this.callParent();
},
items: [
{
xtype: 'grid',
id: 'warteliste-grid',
bind: {
store: '{warteliste}'
},
border: false,
margin: '0 0 20px 0',
selModel: {
allowDeselect: true
},
columns: [
// some grid columns
],
listeners: {
afterRender: function(grid) {
grid.store.load();
}
}
}]
});
I get an error message "Cannot modify ext-empty-store", which must mean that the store is not (yet) bound when store.load() is called in the afterRender listener.
Strange thing is, when I console.log the grid, the store is there. When I console.log grid.store, an empty store is returned.
I got the same issue in afterRender event and solved it by not getting the store from the grid like
grid.store.load();
but from the ViewModel (ViewController scope):
this.getViewModel().getStore('{warteliste}').load();
Check if the store is created as expected in viewmodel. Normally, we do not have store definition files in ./store directory but we place their configurations in viewmodel.
See an example of that here: http://extjs.eu/ext-examples/#bind-grid-form - MainModel::stores
The solution to your original problem
I need different filtered versions of the store in different grids
are chained stores.
See an example of how to implement them here: http://extjs.eu/on-chained-stores/
for me it was
myGrid.getViewModel().getStore('myStoreName').load();