I want to know how to handle a tab or multiple tabs when using Page object model.
My test runs successfully if i don't use the page object model function to run it.
Basically when i click and navigate to the new tab i am using this on the normal test without the POM:
const [newPage] = await Promise.all([
page.waitForEvent('popup'),
page.locator(button...).click();
]);
and then using the newPage as my new tab and it's working.
await newPage.locator(field).fill(testing);
...SNIP..
When using the POM I cant do that and I am not able to continue with the rest of the test, it doesnt recognise the new tab as i cant declare the new page in the POM.
Can someone point me in the right direction ?
How can i implement the same logic using the POM ?
Thanks
Maybe this will help. I also puzzled over how to do it.
constructor(page) {
this.page = page;
this.wait_for_event = page.waitForEvent('popup');
this.your_button = page.locator(button...);
}
async f(){
const [newPage] = await Promise.all([
this.wait_for_event,
this.your_button.click(),
]);
await newPage.getByPlaceholder or another method('placeholder text').fill('');
}
In my case something like this works:
import { test } from '#playwright/test';
import { LoginPage } from '../../pages/LoginPage';
import { ProductsPage } from '../../pages/ProductsPage';
const purchasedProduct = 'Sauce Labs Backpack';
test.beforeEach(async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.goTo('inventory.html');
});
test('As a user I want to open a new tab and visit it', async ({
page,
context,
}) => {
const productsPage = new ProductsPage(page);
const [newPage] = await Promise.all([
context.waitForEvent('page'),
productsPage.openNewTabWithProduct(purchasedProduct),
]);
await newPage.locator('text=Sauce Labs Onesie').click();
});
The crucial part is you have to put "context" instead of page as your test attribute. Then just use your newly created page
Related
I'm using Webdriver.io/Nodejs/Mocha and when I try to switch from parent tab to child tab, majority of the time it works; however, there are times that the tab will take a long time to load due to issues/bad adverts on the page and during those times, even though I get the window/tab GUID, it still doesn't switch and remains on the parent tab. It doesn't happen all the time but occassionally it fails to switch.
Does the page fully have to load to be able to switch to the chid tab? Am I missing anything? Any help would be appreciated. Thanks!
Node version: v16.13.1
WebdriverIO version: 7.16.12
Mocha version: 9.0.0
Chromedriver version: 95.0.0
Execution:
npx mocha --config ./mocharc.json ./test/test.js
Test File: test.js
it('Test Case: Switch Tabs', async () => {
let parentGUID;
let childGUID;
// get parent GUID
parentGUID = await this.driver.getWindowHandle();
// click element to launch new tab
await this.driver.elementClick('//a[#id="test"]');
// wait until new tab loads
await this.driver.pause(2000);
// get all GUID's
const allGUIDs = await this.driver.getWindowHandles();
// check all GUID's and see which one is the child
for (let i = 0; i < allGUIDs.length; i++) {
if (allGUIDs[i] !== parentGUID) {
childGUID = allGUIDs[i];
}
}
// switch to child tab
await this.driver.switchToWindow(childGUID);
// assert content on the new page here
// ...
// ...
// ...
// close tab
await this.driver.closeWindow();
// switch to parent window/tab
await this.driver.switchToWindow(parentGUID);
}
This seems like a simple coding mistake. While you are initiating your parentGUID, you are not assigning any value to it. You are then using it to compare in your for loop. Sometimes, the childGUID is assigned with parent GUID. In this case, the switch seems to be not happening. I have corrected it below.
it('Test Case: Switch Tabs', async () => {
let childGUID;
// get parent GUID
const parentGUID = await this.driver.getWindowHandle();
// click element to launch new tab
await this.driver.elementClick('//a[#id="test"]');
// wait until new tab loads
await this.driver.pause(2000);
// get all GUID's
const allGUIDs = await this.driver.getWindowHandles();
// check all GUID's and see which one is the child
for (let i = 0; i < allGUIDs.length; i++) {
if (allGUIDs[i] !== parentGUID) {
childGUID = allGUIDs[i];
}
}
// switch to child tab
await this.driver.switchToWindow(childGUID);
// assert content on the new page here
// ...
// ...
// ...
// close tab
await this.driver.closeWindow();
// switch to parent window/tab
await this.driver.switchToWindow(parentGUID);
}
I'm currently developing a react app and using normal bootstrap.The command to show a modal and toggle to one works fine; however the hide command doesn't hide the modal unless I make it a property on window.
For example:
const triggerSecondModalScreen = () => {
const element = document.getElementById("signUpModal2");
const currElement = document.getElementById("signUpModal");
if (element && currElement) {
const currentModal = new bootstrap.Modal(currElement);
const secondModal = new bootstrap.Modal(element);
setLoading(false);
// #ts-expect-error
window.modal.hide(); // works fine
// second.modal.hide() doesn't work
new bootstrap.Modal(element).show();
resetForm();
}
}
However, I notice that on Chrome dev tools the _isShown is changing correctly to false
I was able to figure out a fix. The solution for anyone encountering this in the future is to not use the 'new bootstrap.Modal()' constructor syntax but to use the getInstance method on the modal.
Changing my code to the below caused it to work completely fine and without the use for creating a function on the window.
const triggerSecondModalScreen = () => {
const element = document.getElementById("signUpModal2");
const currElement = document.getElementById("signUpModal");
if (element && currElement) {
const currentModal = bootstrap.Modal.getInstance(currElement);
setLoading(false);
currentModal?.hide()
bootstrap.Modal.getInstance(element)?.show();
resetForm();
}
}
I'd really like to submit a form in an iframe using Puppeteer, which I've found I can do pretty easily by going
page.keyboard.press('Enter');
However, for nearly everything else I want to do, all I need to pass around is a reference to the iframe I'm interested in. For instance, I may have a method that fills out and submits a form like so:
// Some other setup script
const page = await context.newPage();
const frame = page.frames().find(frame => frame.name() === 'myFrame'); // Iframe ref
// Utility method
function useTheForm(frame) {
// ...
// Do other misc form setup
// ...
await frame.type('myInput', 'Some Value');
// TODO: Submit the form... somehow...
// "frame.keyboard" doesn't exist. Need some kind of ref like "frame.page"
// frame._frameManager._page.keyboard.press('Enter') works, but is kind of dirty...
}
// Use our utility method
useTheForm(frame);
I'd really like a way to submit the form using the "Enter" key without having to also keep track of and pass around a reference to page as well, but I'm hesitant to use intended-to-be-internal properties that aren't documented in the API.
You can focus an element in the iframe and then press a key with page.keyboard. Here is an example that press Enter on a focused link in an iframe causing iframe navigation (though this navigation seems failed due to site iframe policy):
const puppeteer = require('puppeteer');
(async function main() {
try {
const browser = await puppeteer.launch(
{ headless: false, defaultViewport: null });
const [page] = await browser.pages();
await page.goto('https://example.org/');
const data = await page.evaluate(() => {
document.body.appendChild(document.createElement('iframe')).src =
'https://example.org/?foo=bar';
});
await page.waitFor(3000);
console.log(page.frames().map(frame => frame.url()));
await page.frames()[1].focus('a');
await page.keyboard.press('Enter');
//await browser.close();
} catch (err) {
console.error(err);
}
})();
Error from the Browser console:
https://static.food2fork.com/pastaallavodkaa870.jpg.jpg 404
Trying to display the image on the browser, I don't know if it is a problem from my code or food2fork end.
My index.js:
// always make sure you have the right directory
// import field
import Search from './models/Search';
// import all the function from the view
import * as searchView from './views/searchView'
import {elements} from './views/base';
/* Global state of the app
- Search obj
- current recipe obj
- shopping list object
- liked recipes
*/
// everytime we reload the app, it will be empty
const state = {}
const controlSearch = async () =>{
// 1) Get the query from the view
const query = searchView.getInput();
if(query){
// 2) new search object and add it to state
state.search = new Search(query); // new instance of the search class
// 3) prepare UI for results
// 4) Search for recipes
await state.search.getResults(); // await this promise then render the result
// 5) render result in the UI, reminder u got hit the search button
searchView.renderResult(state.search.result);
}
}
elements.searchForm.addEventListener('submit', e => {
e.preventDefault();
controlSearch();
});
My Search.js:
// this is the external source simply call its name
import axios from 'axios';
// query and then the search result
// class declarition ES6
export default class Search {
constructor(query){
this.query = query;
}
async getResults(){
// fetch is only gonna work for modern browser
// HTTP request axios
// if you enter the invalid the key it will not work
//key is blurred out for stackoverflow
const key = '------------------------';
// return json
// if we can not access it we are going to use the cors proxy
// const proxy = you can use google to search for cors proxy
try{
const res = await axios(`https://www.food2fork.com/api/search?key=${key}&q=${this.query}`);
this.result = res.data.recipes;
// console.log(this.result);
} catch(error){
alert(error);
}
}
}
My searchView.js:
// if we are in the current folder then it is simply base
import {elements} from './base';
// return the input value from the field
// implicit search automatically return
export const getInput =() => elements.searchInput.value;
const renderRecipe = recipe =>{
const markup = `
<li>
<a class="results__link" href="#${recipe.recipe_id}">
<figure class="results__fig">
<img src="${recipe.image_url}.jpg" alt=${recipe.title}>
</figure>
<div class="results__data">
<h4 class="results__name">${recipe.title}</h4>
<p class="results__author">${recipe.publisher}</p>
</div>
</a>
</li>
`;
// insert the html
elements.searchResList.insertAdjacentHTML('beforeend',markup);
}
export const renderResult = recipes => {
recipes.forEach(renderRecipe);
}
My base.js:
// all the DOM element will be in this class object
export const elements = {
searchForm: document.querySelector('.search'),
searchInput: document.querySelector('.search__field'),
searchResList: document.querySelector('.results__list')
}
I am new to the web-Dev and learning by myself. I hope this is not a bad question. I need a experienced mind to help me take a look at this error, since it is not a syntax or logic error. Thanks a lot and have a great day.
https://static.food2fork.com/pastaallavodkaa870.jpg.jpg
Did you mean to add .jpg.jpg?.. if not then take off the last .jpg
https://static.food2fork.com/pastaallavodkaa870.jpg
Remove the duplicate .jpg and it will work.
https://static.food2fork.com/pastaallavodkaa870.jpg
I tried to look for answers, but couldn't find. I want to write a function to delete a previously used test organization before I start my tests in testcafe.
It's quite a time-consuming action if to make it through UI. So I wonder if it is possible to use app actions and write a function to delete my test data?
My thoughts are to perform the next steps:
1. find all test organizations I want to delete
2. iterate through each of them, call ShowDeleteOrgModal() method, and after that call DeleteOrganisation() method.
I saw other test tools provide access to application actions using window().
Is there any way I can implement it in testCafe?
The button selector looks like this.
<button class="button_class" onclick="OurApplicationName.ShowDeleteOrgModal('organisation_id');
return false;">Delete Organisation</button>
We had implemented similar idea in cypress this way:
CleanUpOrgs() {
cy.window().then((win) => {
let numberOfOrgs = win.window.$('tr:contains(' + Cypress.env('testOrgName') + ')').length;
while (numberOfOrgs > 0) {
cy.get('table').contains('tr', Cypress.env('testOrgName')).then(elem => {
let orgId = elem[0].id.replace('OurApplicationName_', '');
cy.window().then((win) => {
win.window.OurApplicationName.ShowDeleteOrgModal(orgId);
win.window.OurApplicationName.DeleteOrganisation();
cy.wait(2000);
});
});
numberOfOrgs--;
}
});
},
How can I get access to the window using TestCafe?
Try using ClientFunction. For example, you can open your modal with the following code:
import { ClientFunction } from 'testcafe';
const showDeleteOrgModal = ClientFunction(organizationId => {
OurApplicationName.ShowDeleteOrgModal(organizationId);
});
fixture`My fixture`
.page`http://www.example.com/`;
test('My Test', async t => {
await showDeleteOrgModal('organisation_id');
});
You can also run the asynchronous code on the client side this way.
UPDATE
I can't provide you with an exact test without access to the testing page.
But I've created an example how this test can look like
import { ClientFunction, Selector, t } from 'testcafe';
import { testOrgName } from './config';
fixture`fixture`
.page`http://url`;
const trElSelector = Selector('table').find('tr').withText(testOrgName);
const cleanUpOrg = ClientFunction(() => {
const trElement = trElSelector();
const orgId = trElement.id.replace('OurApplicationName_', '');
window.OurApplicationName.ShowDeleteOrgModal(orgId);
window.OurApplicationName.DeleteOrganisation();
}, { dependencies: { trElSelector } });
async function cleanUpAllOrgs () {
const numberOfOrgs = await Selector('tr').withText(testOrgName).length;
for (let i = numberOfOrgs; i > 0; i--) {
await cleanUpOrg();
await t.wait(200);
}
}
test('test', async t => {
await cleanUpAllOrgs();
});
I used ClientFunctions, Selectors and the config file for the testOrgName variable (you can learn more about using configurations and environment variables in FAQ).