How to integrate cy.visit(local vs stage url) in a CI environment? - testing

I have a test suite that opens a local url before making a bunch of assertions on some DOM elements.
Now I'd like to integrate Cypress in my CI/CD but I'm unsure on how to tell Cypress to visit the staging url instead of the local one.
Any idea?
it('The body of the page is in viewport', function() {
cy.visit(
'http://mylocalurltovisit.local.com.au:8666/foo'
);
let initialPosition;
const body = cy.get('body');
body.should('have.class', 'pdfView');
cy.get('.body-wrapper').should($el => {
initialPosition = $el.position();
expect(initialPosition.top).equal(0);
expect(initialPosition.left).equal(0);
});
});
I'd like for the visit url to automatically switch to say the staging one (which could be http://staging.com.au/foo) on the CI environment.

One way is to try creating the two different url's for local and staging sites as below in the cypress.json file.
{
"env": {
"project1_user": "admin",
"project1_password": "password",
"localSite" : {
"url" : "http://mylocalurltovisit.local.com.au:8666/foo"
},
"stagingSite" : {
"url" : "http://staging.com.au/foo"
}
}
}
Then receive the urls to const inside the test;
const localUrl = Cypress.env('localSite').url;
const stagingUrl = Cypress.env('stagingSite').url;
You can call in beforeEach or use directly inside the test. Same way you can use for staging site a well.
beforeEach( function() {
cy.visit(localUrl + '/somelogin.php' );
} );

Use an environment variable, but instead of putting it in the config file, pass it in the CI command that starts Cypress, e.g
cypress run --env TARGET=local
cypress run --env TARGET=staging
In the test, you can assign the correct url (once only), using a before().
describe('...', () => {
let url;
before(function() {
url = {
local: 'http://...',
staging: 'http://...'
}[Cypress.env('TARGET')];
})

Related

Playwright - Cookies not set from storageState file from different domains in Chromium

Concept
I'm using the concept of reusing the authentication state via storageState file in Playwright.
Following is my code snippet spread across different files:
Code
playwright.config.json
import type { PlaywrightTestConfig } from '#playwright/test';
import { devices } from '#playwright/test';
const config: PlaywrightTestConfig = {
testDir: './e2e',
reporter: 'html',
globalSetup: require.resolve('./e2e/global-setup.ts'),
use: {
storageState: './e2e/authStorageState.json',
},
projects: [
{
name: 'firefox',
use: {
...devices['Desktop Firefox'],
},
},
{
name: 'webkit',
use: {
...devices['Desktop Safari'],
},
},
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
},
},
],
};
export default config;
global-setup.ts
require('dotenv').config();
import { firefox, FullConfig } from '#playwright/test';
import E2EConstants from './e2e.constants';
const authenticateUser = async () => {
const browser = await firefox.launch();
const page = await browser.newPage();
await page.goto(`${process.env.BASE_URL}/${E2EConstants.LoginPage.URL}`);
await page
.getByLabel(E2EConstants.LoginPage.LABEL.EMAIL)
.fill(process.env.TEST_ADMIN_USERNAME as string);
await page
.getByLabel(E2EConstants.LoginPage.LABEL.PASSWORD)
.fill(process.env.TEST_ADMIN_PASSWORD as string);
await page.getByText(E2EConstants.LoginPage.BUTTON).click();
await page.waitForLoadState('networkidle');
await page.context().storageState({ path: './e2e/authStorageState.json' });
await browser.close();
};
async function globalSetup(_: FullConfig) {
await authenticateUser();
}
export default globalSetup;
This sets all the cookies in the authStorageState.json file. However, some cookies have domain as .b.com and some of them have the domain as a.b.com.
example.spec.ts
require('dotenv').config();
import { test, expect } from '#playwright/test';
test('homepage has same link', async ({ page }) => {
await page.goto(process.env.TEST_URL as string);
await expect(page).toHaveURL(process.env.TEST_URL as string);
});
The TEST_URL is the URL with domain a.b.com that is behind authentication and is accessible only when the user is signed in.
Problem
When I run the tests, I see that the Chromium test fails but the Firefox and Webkit tests pass. The test is unable to sign the user in on Chromium, unlike on other browsers. This is because the auth-related cookies (belonging to a.b.com domain) are not set on Chromium but they are set on other browsers. However, the non-auth-related cookies (belonging to .b.com domain) are set properly on all browsers.
What I tried
I tried manually setting the url of the cookies saved in authStorageState.json file to https://a.b.com after deleting the domain and path keys for the auth-related cookies and then when I run the tests, the Chromium test also passes.
The secure key for all cookies, which were not set, had the value as false. I tried manually changing the "secure": true for all cookies which were not set. Note that the sameSite property is set to "None". This change made the Chromium test cases to pass.
The sameSite key for all cookies, which were not set, had the value "None" while having "secure": false property. I tried manually removing the "sameSite": "None" property for all cookies which were not set. Note that I did not change the secure property. This change made the Chromium test cases to pass.
[Note that all the above points were independently implemented as workarounds and were not done together.]
Requirement
However, because the authStorageState.json file is to be created by default on first sign-in, I want to persist the same cookies that appear on browser instead of manually manipulating them. How can I get my Chromium test cases to pass?
Doubts
What is the difference between providing url instead of domain + path for Chromium?
Why do the cookies with subdomain a.b.com require secure property to be set to true and the Chromium-based test cases pass after this change?
Is removing the "sameSite": "None" for all the cookies with subdomain a.b.com right and why do the Chromium-based test cases pass after this change?

CypressIO tests to be executed in multiple URL

An Ecommerce website have different URL's for different countries. And each of these website have to be tested. We have set of automation scripts written in CYPRESSIO. Looking for ideas how these scripts can be rerun for different URL's.
Example URL's for different countries
UK: https://www.abc.co.uk
CH: https://www.abc.ch
DE: https://www.abc.de
There are some functionalities which are country specific and hence we have to run tests for all the URL's . Any ideas and leads would be appreciated.
Thanks in advance :)
Set the tests inside a data loop
const urls = ['https://www.abc.co.uk', 'https://www.abc.ch', 'https://www.abc.de'];
urls.forEach(url => {
describe(`Testing url: ${url}`, () => {
before(Cypress.config('baseUrl', url))
it('...', () => {
})
})
Testing a simplified scenario,
support/index.js
beforeEach(() => {
console.log('beforeEach in support', Cypress.config('baseUrl'))
})
dynamic-baseUrl.spec.js
const urls = ['https://www.abc.co.uk', 'https://www.abc.ch', 'https://www.abc.de'];
urls.forEach(url => {
describe(`Testing url: ${url}`, () => {
before(() => Cypress.config('baseUrl', url))
it('sees the required baseURL', () => {
console.log('it', Cypress.config('baseUrl'))
})
})
})
console output
beforeEach in support https://www.abc.co.uk
it https://www.abc.co.uk
beforeEach in support https://www.abc.ch
it https://www.abc.ch
beforeEach in support https://www.abc.de
it https://www.abc.de
Cypress log
Testing url: https://www.abc.co.uk
...passed
Testing url: https://www.abc.ch
...passed
Testing url: https://www.abc.de
...passed
One way would be to create country-specific cypress.json files.
For eg. cypress-de.json
Inside it you can define baseURL and country-specific test cases using testFiles[] as:
{
"testFiles": [
"TC_01.spec.js",
"TC_02.spec.js",
"TC_03.spec.js",
"TC_04.spec.js"
],
"baseUrl": "https://www.abc.de"
}
Now when you want to run tests, all you have to do is pass the relevant cypress.json file through CLI using the command:
npx cypress run --config-file cypress-de.json

Nuxt.js env Property, understanding and how to use it?

following https://nuxtjs.org/api/configuration-env
I have been trying to set up my apiUrl in nuxt.config.js once for the whole project, like:
export default {
env: {
apiUrl: process.env.MY_REMOTE_CMS_API_URL || 'http://localhost:1337'
}
}
adding this in nuxt.config.js, I'd expect (and would like) to have apiUrl accessible everywhere in the project.
In particular, it is needed for the 3 following cases:
with axios, to generate static pages from dynamic urls (in nuxt.config.js)
generate: {
routes: function () {
return axios.get(apiUrl + '/posts')
.then((res) => {
return res.data.filter(page => {
return page.publish === true;
}).map(page => {
return {
route: '/news/' + page.slug
}
})
})
}
},
with apollo, to get data via graphql (in nuxt.config.js)
apollo: {
clientConfigs: {
default: {
httpEndpoint: apiUrl + '/graphql'
}
}
},
in every layout, page and components, as the base url of media:
<img :src="apiUrl + item.image.url" />
As you might see, only thing I need is to 'print' the actual base url of the cms.
I have also tried to access it with process.env.apiUrl, with no success.
The only way I was able to make it has been to create an extra plugin/apiUrl.js file, which injects the api url, and seems wrong to me as I am now setting the apiUrl twice in my project.
I asked this question in the past, but in a way less clear way. I was suggested to use dotenv, but from the docs it looks like adding an additional layer of complication that might not be necessary for a simpler setup.
Thanks.
I think dotenv module really is what you need.
This is my setup:
Project root has a .env file that contains
BASE_URL=https://www.myapi.com
require('dotenv').config() at top of nuxt.config.js
#nuxtjs/dotenv installed and added to buildModules of nuxt.config.js
env: { BASE_URL: process.env.BASE_URL} added to nuxt.config.js
axios: { baseURL: process.env.BASE_URL } added to nuxt.config.js (optional)
You should have access to your .env throughout the project. (process.env.BASE_URL)
I haven't used apollo, but you should be able to set the apollo endpoint with process.env.BASE_URL + '/graphql'
As of Nuxt 2.13, #nuxtjs/dotenv is not required anymore. Read here
The concept that I was missing is that you set up the same named variable in your server / pipeline, so that you have your (always local / never pushed) .env file and a same name variable remotely, not added to your repo (where the value can be the same or different)

Confirm URL using POM Nightwatch

New to nightwatch and js in general and I'm struggling to figure out how to validate a url using pom pattern. I know this is wrong. Any suggestions are greatly appreciated, including useful links for me to rtfm as I'm struggling to find robust examples of pom nightwatch.
test.js
module.exports = {
"tags": ['sanity'],
'confirm navigation to example.com' : function (client) {
var landingPage = client.page.landing();
landingPage.navigate();
landingPage.confirmOnLandingPage();
client.end();
}
};
page.js
var landingCommands = {
navigate:function(){
url: 'example.com/'
},
confirmOnLandingPage:function(){
this.expect.element('body')
.to.be.present.before(1000)
.url(function(result)
{
this.assert.equal(result.value, 'example.com/', 'On Landing Page.')
});
}
}
Running: confirm navigation to example.com
✖ TypeError:
this.expect.element(...).to.be.present.before(...).url is not a
function
at Page.confirmOnLandingPage (/Users/Home/Development/NWQA/pages/page.js:9:10)
at Object.confirm navigation to example.com (/Users/Home/Development/NWQA/tests/test.js:7:21)
FAILED: 1 errors (18ms)
After running .expect you break the Nightwatch command chain and start Expect.js chain so after you call this.expect.element('body').to.be.present.before(1000) you get Expect.js object and not Nightwatch browser object.
To fix just start a new chain and change this.url call to this.api.url since url() is not available within the page object:
confirmOnLandingPage: function(){
this.expect.element('body').to.be.present.before(1000);
this.api.url(function(result)
{
this.assert.equal(result.value, 'example.com/', 'On Landing Page.')
});
}
Update:
I just noticed you incorrectly declared URL for your page object. navigate is internal function, you only need to provide url property:
var landingCommands = {
url: 'example.com/',
...
};

WebdriverIO: How to read baseURL value from wdio.conf.js. inside step definition file

I am using WebdriverIO for test automation. In wdio.conf.js file I have configured the 'baseUrl' property.
I want to read the 'baseUrl' property value inside my test .js file. How can I do this?
❒ wdio-v5
Lately, after writing a lot of tests for a project rewrite I've came to believe the best way to store/access global config variables is via the global object.
You can define them inside the wdio.conf.js file's hooks. I defined mine in the before hook:
before: function (capabilities, specs) {
// =================
// Assertion Library
// =================
const chai = require('chai');
global.expect = chai.expect;
global.assert = chai.assert;
global.should = chai.should();
// ======================
// Miscellaneous Packages
// ======================
global.langCode = langCode;
global.countryCode = countryCode;
global.request = require('superagent');
global.allowedStatusCodes = [200, 301],
// ===============
// Custom Commands
// ===============
require('./test/custom_commands/aFancyMethod');
require('./test/custom_commands/anotherOne');
require('./test/custom_commands/andAnotherOne');
},
Then, you can access them directly, anywhere in your test-files, or page-objects. This way, you greatly reduce the test-file's footprint (errr... codeprint) because you can call these directly in your test case:
describe(`Testing a random URL`, () => {
it('Should return a HTTP valid status code', async () => {
// Issue a HTTP request for the given URL:
await request
.head('https://random.org')
.then(res => {
console.info(`\n> Status code found: ${res.status} | MIME type found: '${res.type}'\n`);
foundStatusCode = res.status;
})
.catch(err => {
console.info(`\n> Status code found: ${err.status} | Error response found: '${JSON.stringify(err.response)}'\n`);
foundStatusCode = err.status;
});
// Assert the HTTP Status Code:
assert.include(allowedStatusCodes, foundStatusCode, `!AssertError: Route yields a bad status code! Got: ${foundStatusCode} | Expected: ${allowedStatusCodes}`);
});
As opposed to always doing await browser.options.request.head(..., browser.options.baseUrl, etc.
❒ wdio-v4
All the wdio.conf.js file attributes (basically the config object name-value pairs) are also stored inside the browser.options object.
Thus, a more elegant approach to access your global config values from inside your tests would be as presented below:
> browser.options
{ port: 4444,
protocol: 'http',
waitforTimeout: 10000,
waitforInterval: 500,
coloredLogs: true,
deprecationWarnings: false,
logLevel: 'verbose',
baseUrl: 'http://localhost',
// ... etc ...
}
> browser.options.baseUrl
'http://localhost'
I'll go on a limb here and presume you want to read the baseUrl value from your wdio.config.js file, into your test.js file.
TL;DR: In your test.js file heading, add the following:
var config = require('<pathToWdioConfJS>/wdio.conf.js').config;
You can then access any wdio.config.js value via the config.<configOption>, in your case config.baseUrl.
Lastly, I would greatly recommend you read about exports and module exports.
WebdriverIO is built on NodeJS, so you will shoot yourself in the foot on the long run if you don't know how and when to use exports, module.exports, require, or the difference between them.
Use browser.options.baseUrl . If you use require, you're hard coding from that one file, which is fine, but you cannot do a wdio --baseUrl=http://myTestSite2.net to override the "global" baseUrl. Which you might want to do in multiple deployments in the future.
In wdio.config.js file define the url like this
var baseUrl = 'YOUR URL'
exports.config = {
baseUrl: baseUrl,
}
In Test file use / instead of adding complete url in browser.url('/'), it will use the baseUrl from the wdio.config.js file.
browser.url('/')
BaseUrl is available in the config object browser.config.baseUrl
See https://github.com/webdriverio/webdriverio/blob/a4a5a46f786f8548361d7f86834b38f89dcb1690/packages/webdriverio/webdriverio-core.d.ts#L131
just save all your variable in before: function and can be used anywhere in your test.
like the following example i use retry count wdio config file
before: function (capabilities, specs) {
expect = require('chai').expect;
should = require('chai').should();
assert = require('assert');
retryCount=2;
browser.maximizeWindow();