a quick question for anyone with React + Cypress experience - writing my first set of E2E tests and here's whats bugging me :
cy.visit('http://movinito.docker.localhost:3000/company/subcontractors');
works, but
cy.visit('/company/subcontractors');
doesn't work as expected (redirects me to the dashboard after login and stays there when I try to assert a pathname includes 'subcontractors').
my baseUrl in the cypress.json is
{"baseUrl": "http://react_frontend.movinito.docker.localhost:3000"}
and it generally works (in case that was what you suspected).
I would like to use the shorter, nicer version cy.visit('/company/subcontractors'); instead of the long winded retype of the baseUrl...
Might be important to add that prior to the .visit I use a
cy.request('POST', 'http://movinito.docker.localhost/user/login?_format=json', {name,pass});
to [successfully] log in... As I said the whole thing works but I can't make use of the baseUrl and have to use the .visit command with the full environement based url...
Here is the [working] full test code
describe('Subcontractors section', ()=> {
it('renders properly', ()=> {
const { name, pass } = {name: 'info#batcave.com', pass: '123#456'}
cy.request('POST', 'http://movinito.docker.localhost/user/login?_format=json', {
name,
pass
});
cy.visit('http://movinito.docker.localhost:3000/company/subcontractors');
//
// I want to replace the above line with cy.visit('/company/subcontractors')
//
cy.location('pathname').should('include', '/company/subcontractors');
cy.get('[data-cy=page-title]').should('have.text', 'Subcontractors');
})
});
hmm, I read the documentation about visit() and request(), this should work to:
describe('Subcontractors section', ()=> {
it('renders properly', ()=> {
cy.visit({
url: 'http://movinito.docker.localhost/user/login?_format=json',
method: 'POST',
body {
name,
pass
}
})
cy.visit('/company/subcontractors')
cy.location('pathname').should('include', '/company/subcontractors')
})
});
// cypress.json
{
"baseUrl": "http://react_frontend.movinito.docker.localhost:3000"
}
Related
I am trying a test case with the help of the fixtures in Cypress. I have got the userdata.json folder under the ../cypress/fixtures folder.
When I try to run the test I always get this error TypeError Cannot read properties of undefined (reading 'assets')
Please find the code and other details here:
The .json file looks like this:
{
"email" : "****",
"password" : "****",
"title" : "dashboard",
"number" : "12",
"assets" : ["birthday-cake.jpg","cupcake.jpg","cake-black.jpg","building-1.jpg","building-2.jpg","cake-1.jpg","cake-2.jpg","cake-3.jpg","cake-4.jpg","car-1.jpg","car-2.jpg","car-3.png","longvideo.mp4","shortvideo.mp4","skyscrapper.jpg"]
}
My code looks like this :
/// <reference types="cypress" />
describe('Getting the names of the assets and iterating to find repeated names', ()=> {
before(function() {
cy.fixture('userdata').then(function(value){
this.value=value
})
})
it('login',function() {
cy.visit('https://sample-staging.com/login')
cy.url().should('include','staging')
cy.get('input[type="email"]').type(this.value.email).should('have.value',this.value.email)
cy.get('input[type="password"]').type(this.value.password).should('have.value',this.value.password)
cy.get('button').contains('Login').click()
cy.wait(5000)
cy.url().should('include',this.value.title)
})
it('Creating a new playlist', function() {
cy.get('.text-xfm').contains('PLAYLISTS').click()
cy.wait(5000)
cy.get('div.text-xfs').contains('Add New').click()
cy.get('label.text-xfx').should('contain','Playlist name is required')
cy.get('#playlistName').type('Test-Playlist').should('have.value','Test-Playlist')
cy.get('span.text-xfs').should('have.text','Choose assets to add to playlist').click()
cy.wait(5000)
cy.get('div.overflow-scroll').should('be.visible')
cy.log(this.value.assets)
this.value.assets.forEach(function(element) {
cy.selectAsset(element)
});
})
})
I have also created a custom command - selectAsset which is in the commands.json
I am not sure what I missed I tried all the answers that I have seen so far but always I get this error.
Find the screenshot here -
Test case passing
Throwing error
As it can be seen all the places where I use the fixture like email, password, title are all passing except the array - assets.
Help is much appreciated. Thanks in advance.
Instead of before you have to use beforeEach because cypress clears aliases between tests. So your first it block is able to access the fixture values where as the second one isn't.
From Cypress Docs
Under the hood, aliasing basic objects and primitives utilizes Mocha's
shared context object: that is, aliases are available as this.*.
Mocha automatically shares contexts for us across all applicable hooks
for each test. Additionally these aliases and properties are
automatically cleaned up after each test.
describe('Getting the names of the assets and iterating to find repeated names', () => {
beforeEach(function () {
cy.fixture('userdata').then(function (value) {
this.value = value
})
})
it('login', function () {
cy.visit('https://manager-staging.xogo.io/login')
cy.url().should('include', 'staging')
cy.get('input[type="email"]')
.type(this.value.email)
.should('have.value', this.value.email)
cy.get('input[type="password"]')
.type(this.value.password)
.should('have.value', this.value.password)
cy.get('button').contains('Login').click()
cy.wait(5000)
cy.url().should('include', this.value.title)
})
it('Creating a new playlist', function () {
cy.get('.text-xfm').contains('PLAYLISTS').click()
cy.wait(5000)
cy.get('div.text-xfs').contains('Add New').click()
cy.get('label.text-xfx').should('contain', 'Playlist name is required')
cy.get('#playlistName')
.type('Test-Playlist')
.should('have.value', 'Test-Playlist')
cy.get('span.text-xfs')
.should('have.text', 'Choose assets to add to playlist')
.click()
cy.wait(5000)
cy.get('div.overflow-scroll').should('be.visible')
cy.log(this.value.assets)
this.value.assets.forEach(function (element) {
cy.selectAsset(element)
})
})
})
Looking up several examples online, it appears all one would need to do to redirect requests to the backend is to add rewrites to the next.config.js file, and it should work. However, I must be missing or misunderstanding something as this alone doesn't seem to do the trick. Redirecting seems to work if I type the url in the browser, but calls from axios/fetch continue to try to use a path relative to my client. Here are my snippets:
next.config.js
module.exports = {
async rewrites() {
return [
{
source: '/api',
destination: 'http://localhost:3001',
},
]
},
};
components/MyComponent.js
import React, { useEffect } from "react";
import axios from "axios";
function MyComponent({projectName}) {
...
useEffect(() => {
axios.get("/api/project/" + projectName)
.then((res) => {
console.log(res);
});
return;
}, []);
...
};
export default MyComponent;
To clarify, if I hit http://localhost:8001/api/project/Name_of_Project from the browser, I get properly redirected to my server (hosted on port 3001) and receive data I'd expect. However, when I hit my client (http://localhost:8001/Name_of_Project), axios doesn't redirect and tries http://localhost:8001/api/project/Name_of_Project which obviously fails. I also tried the fetch equivalent instead of axios and get the same result.
Is there another step that I need to take? Does the rewrite not work for axios/fetch? I have also seen mentions of the next-http-proxy-middleware package in my search, but I am not sure if this is something that I need to use in conjunction with the rewrite or not.
I appreciate any insight!
EDIT 1:
After doing some more searching, I ran into this post, and discovered that my issue is because I am using relative pathing in my axios call. If I change it to:
axios.get("http://localhost:3001/api/project/" + projectName)
.then((res) => {
console.log(res);
});
then I get my data properly. I suppose this leads me to my next question: is there a way to use relative path alongside the rewrite in the config? I personally think it's a little ugly to have the hostname and port exposed like that (I eventually plan on hosting this app on a FQDN). So if there's anything that can be done about that, I'd love to know!
EDIT 2: Of course the change in my first edit works because I am hitting my server directly! Which is not the desired effect. I want to use the redirect set in the config to go to my api.
Aha! I WAS misunderstanding something. I assumed that the path in the rewrite would reattached my path params for free. This is not the case. A link to the documentation.
The relevant excerpt:
Wildcard Path Matching
To match a wildcard path you can use * after a parameter, for example /blog/:slug* will match /blog/a/b/c/d/hello-world:
module.exports = {
async rewrites() {
return [
{
source: '/blog/:slug*',
destination: '/news/:slug*', // Matched parameters can be used in the destination
},
]
},
}
So my corrected next.config.js:
module.exports = {
async rewrites() {
return [
{
source: '/api/:slug*',
destination: 'http://localhost:3001/api/:slug*',
},
]
},
};
I am trying to find a way to set the browser configurations for Puppeteer once so that I do not have to do it for every
test case.
Currently, this is the structure I'm working with:
jest.config.js
process.env.BROWSER_PATH='<PATH_TO_CHROME>/chrome.exe';
module.exports = {
preset: "jest-puppeteer",
globals: {
URL: "http:localhost:3000",
HEADLESS: (process.env.NODE_ENV==='development' ||
process.env.NODE_ENV==='production'
? true : false
)
},
testMatch: [
"**/test/**/*.test.js"
],
verbose: true
}
jest-puppeteer.config.js
module.exports = {
launch: {
headless: HEADLESS,
slwoMo: process.env.SLOWMO ? process.env.SLOWMO : 0,
devtools: false,
executablePath: process.env.BROWSER_PATH,
}
}
browserTest.js
describe('Browser Test', () => {
// This test has 2 asserts
test('Browser Popup: successful', async () => {
console.log("[BrowserTest] headless:", HEADLESS);
try {
const browser = await puppeteer.launch({
// headless: process.env.HEADLESS == 'true'? true:false,
// executablePath: process.env.BROWSER_PATH,
isMobile: false,
});
const page = await browser.newPage();
await page.goto(URL, {
waitUntil: 'domcontentloaded'
});
page.once('load', () => console.log('[BrowserPopup] Page loaded!'));
await new Promise(resolve => setTimeout(resolve, 7500));
await browser.close();
} catch (e) {
console.log('Err: ', e);
}
}, timeout);
});
The idea is that if I have headless and executablePath in the launch configurations (in jest-puppeteer.config.js), then shouldn't puppeteer pick it up from there? Why do I have to re-declare it when launching the browser in my test case?
The alternative I thought of was that if I cannot do this, then I want to be able to replicate this behavior in the process.env.HEADLESS as shown in the first line jest.config.js. I'd remove it from the globals declaration of the config file. This method does not work either.
Question: Is there a singular place I could put this so that I don't have to keep declaring this? The HEADLESS state depends on its environment it is being executed in.
Note: Currently, this throws an error because jest-puppeteer.config.js cannot see jest.config.js. So its a 'HEADLESS is not defined' error. After fixing it, it threw another error that Chrome was not defined. So I had to put that in the puppeteer.launch({}) parameters as well. Which leads me to believe that jest-puppeteer.js isn't really doing anything, since it can't see headless or executablePath.
Note: I run the tests using jest --config=jest.config.js. This works because I can use the global variable in a successful test run. Thanks for all the help!
This is crucial think in cypress.io and does not work (Assertion fails but test is successful). Full code below. I have tried to rewrite my code many times, without success. Installed latest NPM, latest Node.JS. Deleted node_modules and reinstalled dependencies.
/* eslint-disable no-undef */
describe("Contact us form", () => {
beforeEach(() => {
cy.visit("/");
cy.contains("Contact us").click();
cy.location().should(loc => {
expect(loc.pathname).to.eq("/contact-us");
});
});
it.only("Fill Contact us form - error 500", () => {
cy.server();
cy.route({
method: "POST",
url: "/api/v1/email/contact-us",
status: 500,
response: {}
}).as("sendMessage");
cy.getTestElement("ContactUs__form__container").within(() => {
cy.get("#users").then(users => {
const user = users.admin;
cy.get("#name")
.type(user.name)
.should("have.value", user.name);
cy.get("#email")
.type(user.email)
.should("have.value", user.email);
cy.get("#phone")
.type(user.phone)
.should("have.value", user.phone);
const message = Cypress.config("testVars").testMessage;
cy.get("#message")
.type(message)
.should("have.value", message);
});
cy.get("button:first").click(); // "Send message"
});
cy.wait("#sendMessage");
cy.get("#alert-dialog-description").contains("My App");
});
});
Known issue?
https://github.com/cypress-io/cypress/issues/3497
Are you running the latest cypress (3.1.5)?
As far as I know, you cannot automate captcha
I decided use framework CodeceptJS and library Nightmare.
Mine issue is set cookie before run all test suite
I read the documentation and understanding so for that to solve mine issue me need use helper classes. Maybe I'm wrong but still.
Perhaps you need to use a different approach if so let me know.
It mine helper
'use strict';
class SetCookie extends Helper {
constructor(config){
super(config)
}
_beforeSuite() {
this.client = this.helpers['Nightmare'].browser;
this.getCookies().then((cookies) => {
console.log(cookies);
})
}
getCookies(){
return this.client.cookies.get()
}
}
module.exports = SetCookie;
Problem
Cookies return after finished test suite
https://codecept.io/helpers/Nightmare/#setcookie
That is existing nightmare helper. Did you try it?
I solved this problem:
Mine Helper
async setSplitCookies() {
const client = this.helpers['Nightmare'];
const cookie = {
name: 'rc_web_spl',
value: 'on',
url: 'http://www.nic.ru'
}
return client.setCookie(cookie);
}
Mine test:
Before((I) => {
I.setSplitCookies();
});