TypeScript Playwright: Saving Storagestate via Sign in via API request and then using inside BowserContext does not work - takes me back to login page - authentication

I need to decide whether to go with Playwright as an E2E Automation tool or not.
The following piece of code using backend API for sign in is working fine.
But when I use the saved Storagestate file inside the BrowserContext, then it fails to sign me in, hence taking me back to the Login page.
test.describe('User login', async () => {
var browser: any;
test.beforeEach(async () => {
browser = await chromium.launch({
channel: 'chrome',
headless: false
});
});
const requestContext = await request.newContext();
const response = await requestContext.post(`${testConfig.envConfig.apiURL}/login/authorize`, {
form: {
'email': 'user.tech+admin#xxxx.com',
'password': 'xxxxxx'
}
});
const responseJson = await response.json();
console.log(responseJson);
expect(response.ok()).toBeTruthy();
// Save signed-in state.
await requestContext.storageState({ path: './.storagestates/adminUserStorageState.json' });
await requestContext.dispose();
// Using the Storagesate in the BrowserContext
// page should sign in as a admin user.
test.use({ storageState: './.storagestates/adminUserStorageState.json' });
test('Version 1: Cookies stored via Auth. by backend API', async ({ page }) => {
let url = `/brand/f99f06fb1f121234aaf/gallery`;
await page.goto(url, {timeout: 60000});
});
test('Version 2: Cookies stored via Auth. by backend API', async () => {
// page should sign in as a buyer user.
const userContext = await browser.newContext({ storageState: './.storagestates/buyerUserStorageState.json' });
const page = await userContext.newPage();
await page.goto(`/`, {timeout: 60000});
});
});

Related

Is it possible to mock responses during a detox end-to-end test

I'm currently making a react-native mobile application. I try to test my login button and fields and want to test the logic of moving to the account screen upon login.
Currently I got this as my testcase:
import {clickAccountButton} from "./Navigation";
//API
import nock from "nock";
import {API_URL} from "#env";
import {axiosPost} from "../app/config/api";
jest.useFakeTimers();
describe('LoginScreen tests', function () {
beforeAll(async () => {
await device.launchApp();
});
it('should show the login screen', async () => {
await clickAccountButton();
await expect(element(by.id('loginScreen'))).toBeVisible();
});
it('should have the header', async () => {
await expect(element(by.id('header'))).toBeVisible();
});
it('should contain email and password form field', async () => {
await expect(element(by.id('email'))).toBeVisible();
await expect(element(by.id('password'))).toBeVisible();
});
it('should contain the login and forgotten password buttons', async () => {
await expect(element(by.id('loginSubmit'))).toBeVisible();
await expect(element(by.id('forgotPassword'))).toBeVisible();
});
it('should navigate to ForgotPassword onPress', async () => {
await element(by.id('forgotPassword')).tap();
await expect(element(by.id('forgotPasswordScreen'))).toBeVisible();
//Click the account button again to return to the login screen
await clickAccountButton();
});
it('should login successfully', async () => {
//Give the following response to the next httpRequest
nock(`${API_URL}`)
.post('/api/v1/auth/login')
.reply(200, {
loggedIn: true,
user: {
id: 1,
}
}).persist();
await element(by.id('email')).replaceText('hello#email.com');
await element(by.id('password')).replaceText('foobar');
await element(by.id('loginSubmit')).tap();
//Double check: Check if the view with testID 'welcomeScreen' is showing
//and the input field with testID 'email' is gone
await expect(element(by.id('accountScreen'))).toBeVisible();
await expect(element(by.id('email'))).not.toBeVisible();
});
});
I expect the 'should login successfully' case to succeed because i'm intercepting the request and nock sends a response. This is not the case though. Instead it just does the actual request to my local API server which I don't want to use. Cause I don't want actual login details in my test.
Does anyone know how to handle this? Thanks in advance!
Jest and the app under test run in separate processes so normal Jest mocking techniques such as Nock won't work. Have a look at the mocking guide for Detox.
If you have a module such as apiClient.js in your app then you can mock that with something like apiClient.mock.js.

Get localStorage in NextJs getInitialProps

I working with localStorage token in my next.js application. I tried to get the localStorage on page getInitialProps but, it returns undefined.
Here is an example,
Dashboard.getInitialProps = async () => {
const token = localStorage.getItem('auth');
const res = await fetch(`${process.env.API_URL}/pages/about`, {
headers: { 'Authorization': token }
});
const data = await res.json();
return { page: data };
}
For the initial page load, getInitialProps will run on the server
only. getInitialProps will then run on the client when navigating to a
different route via the next/link component or by using next/router. Docs
This means you will not be able to access localStorage(client-side-only) all the time and will have to handle it:
Dashboard.getInitialProps = async ({ req }) => {
let token;
if (req) {
// server
return { page: {} };
} else {
// client
const token = localStorage.getItem("auth");
const res = await fetch(`${process.env.API_URL}/pages/about`, {
headers: { Authorization: token },
});
const data = await res.json();
return { page: data };
}
};
If you want to get the user's token for the initial page load, you have to store the token in cookies instead of localStorage which #alejandro also mentioned in the comment.

Trying to set a cookie established on a web session as a header back to API

I am trying to login via the webfront end and trying to intercept a cookie and then using that in the subsequent API request. I am having trouble getting the cookie back into the GET request. Code posted below.
import https from 'https';
import { bitbucketUser } from "../userRole.js"
import { ClientFunction } from 'testcafe';
fixture `Request/Response API`
// .page `https://myurl.company.com/login`
.beforeEach(async t => {
await t.useRole(bitbucketUser)
});
test('test', async t => {
const getCookie = ClientFunction(() => {
return document.cookie;
});
var mycookie = await getCookie()
const setCookie = ClientFunction(mycookie => {
document.cookie = mycookie;
});
var validatecookie = await getCookie()
console.log(validatecookie)
const executeRequest = () => {
return new Promise(resolve => {
const options = {
hostname: 'myurl.company.com',
path: '/v1/api/policy',
method: 'GET',
headers: {
'accept': 'application/json;charset=UTF-8',
'content-type': 'application/json'
}
};
const req = https.request(options, res => {
console.log('statusCode:', res.statusCode);
console.log('headers:', res.headers);
let body = "";
res.on("data", data => {
body += data;
});
res.on("end", () => {
body = JSON.parse(body);
console.log(body);
});
resolve();
});
req.on('error', e => {
console.error(e);
});
req.end();
});
};
await setCookie(mycookie)
await executeRequest();
});
I have tried several examples but am quite not able to figure what is it that I am missing.
When you call the setCookie method, you modify cookies in your browser using the ClientFunction.
However, when you call your executeRequest method, you run it on the server side using the nodejs library. When you set cookies on the client, this will not affect your request sent from the server side. You need to add cookie information directly to your options object as described in the following thread: How do I create a HTTP Client Request with a cookie?.
In TestCafe v1.20.0 and later, you can send HTTP requests in your tests using the t.request method. You can also use the withCredentials option to attach all cookies to a request.
Please also note that TestCafe also offers a cookie management API to set/get/delete cookies including HTTPOnly.

logging in with cypress/puppeteer

I have an application that uses Okta to log in. I am trying to set up an automation test using Cypress with Puppeteer. The problem I am having is that when I launch puppeteer with {headless: true}, it doesn't fill in the login form. {headless: false} opens another browser window outside of Cypress and works as expected. It fills in the login form, logs in correctly, but I can't run any cypress tests in that window.
on('task', {
login({username, password}) {
return (async () => {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
await page.goto(config.baseUrl);
await page.waitForNavigation();
await page.waitFor('#okta-signin-username');
await page.type('#okta-signin-username', username, {delay: 100});
await page.waitFor('#okta-signin-password');
await page.type('#okta-signin-password', password, {delay: 100});
await page.click('#okta-signin-submit');
await page.waitForNavigation();
return true;
};
});
Here's how it's being used:
it('my test', () => {
cy.visit('/');
cy.viewport(1280, 1024);
cy.login('username', 'password');
//tests go here
});
The above code opens Chromium and correctly logs in but then the tests fail. It doesn't log in with {headless: true}. Am I missing something here?
Any help would be appreciated.
Thanks

How do I split my Jest + Puppeteer tests in multiple files?

I am writing automated tests using Jest & Puppeteer for a Front-end application written in Vue.js
So far I managed to write a set of tests, but they all reside in the same file:
import puppeteer from 'puppeteer';
import faker from 'faker';
let page;
let browser;
const width = 860;
const height = 1080;
const homepage = 'http://localhost:8001/brt/';
const timeout = 1000 * 16;
beforeAll(async () => {
browser = await puppeteer.launch({
headless: false, // set to false if you want to see tests running live
slowMo: 30, // ms amount Puppeteer operations are slowed down by
args: [`--window-size=${width},${height}`],
});
page = await browser.newPage();
await page.setViewport({ width, height });
});
afterAll(() => {
browser.close();
});
describe('Homepage buttons', () => {
test('Gallery Button', async () => {
// navigate to the login view
await page.goto(homepage);
await page.waitFor(1000 * 0.5); // without this, the test gets stuck :(
await page.waitForSelector('[data-testid="navBarLoginBtn"]');
await page.click('[data-testid="navBarLoginBtn"]'),
await page.waitForSelector('[data-testid="navBarGalleryBtn"]');
await page.click('[data-testid="navBarGalleryBtn"]'),
// test: check if we got to the gallery view (by checking nr of tutorials)
await page.waitForSelector('.card-header');
const srcResultNumber = await page.$$eval('.card-header', (headers) => headers.length);
expect(srcResultNumber).toBeGreaterThan(1);
}, timeout);
});
describe('Register', () => {
const btnLoginToRegister = '#btn-login-to-register';
const btnRegister = '#btn-register';
const btnToLogin = '#btn-goto-login';
test('Register failed attempt: empty fields', async () => {
// navigate to the register form page via the login button
await page.goto(homepage);
await page.waitForSelector(navLoginBtn);
await page.click(navLoginBtn);
await page.waitForSelector(btnLoginToRegister);
await page.click(btnLoginToRegister);
// test; checking for error messages
await page.waitForSelector(btnRegister);
await page.click(btnRegister);
const errNumber = await page.$$eval('#errMessage', (err) => err.length);
expect(errNumber).toEqual(3);
}, timeout);
test('Register failed: invalid char count, email format', async () => {
// fill inputs
await page.waitForSelector('#userInput');
await page.type('#userInput', 'a');
await page.waitForSelector('#emailInput');
await page.type('#emailInput', 'a');
await page.waitForSelector('#emailInput');
await page.type('#passInput', 'a');
await page.waitForSelector(btnRegister);
await page.click(btnRegister);
// test: check if we 3 errors (one for each row), from the front end validations
const err = await page.$$eval('#errMessage', (errors) => errors.length);
expect(err).toEqual(3);
}, timeout);
test('Register: success', async () => {
await page.click('#userInput', { clickCount: 3 });
await page.type('#userInput', name1);
await page.click('#emailInput', { clickCount: 3 });
await page.type('#emailInput', email1);
await page.click('#passInput', { clickCount: 3 });
await page.type('#passInput', password1);
await page.waitForSelector(btnRegister);
await page.click(btnRegister);
// test: check if go to login link appeared
await page.waitForSelector(btnToLogin);
await page.click(btnToLogin);
// await Promise.all([
// page.click(btnToLogin),
// page.waitForNavigation(),
// ]);
}, timeout);
test('Register failed: email already taken', async () => {
// navigate back to the register form
await page.waitForSelector(btnLoginToRegister);
await page.click(btnLoginToRegister);
await page.click('#userInput');
await page.type('#userInput', name2);
await page.click('#emailInput');
await page.type('#emailInput', email1); // <- existing email
await page.click('#passInput');
await page.type('#passInput', password2);
await page.click(btnRegister);
const err = await page.$eval('#errMessage', (e) => e.innerHTML);
expect(err).toEqual('Email already taken');
}, timeout);
});
I would like to be able to have a single test file that does the beforeAll and afterAll stuff, and each test suite: HomepageButtons, Register, etc. to reside in it's own test file. How would I be able to achieve this?
I've tried splitting tets into:
testsUtils.js that would contain the beforeAll and afterAll hooks and code but it doesn't guarantee that it runs when it needs: the beforeAll code to fire before all other test files and the afterAll code to fire after all the test files finished.
Sorry, I'd rather comment on your question, but I don't have reputation for that. Anyway, I think that you are looking for something like a "global beforeAll" and "global afterAll" hooks, right? Jest has it alread. It's called "globalSetup" and "globalTeardown".
Take a look at globalSetup. Excerpt:
This option allows the use of a custom global setup module which
exports an async function that is triggered once before all test
suites.
The Global Teardown one goes the same.
I think you'll have a headache trying to get a reference to the page or browser in globalSetup/globalTeardown and I confess that I never try this. Maybe the answer for that problem (if you have it) is on this page, under "Custom example without jest-puppeteer preset section.
Also there is a repo that tries to facilitate Jest + Puppeteer integration. Maybe you find it util: repo.
Good luck. :)