I'm trying to put in a class the puppeter call, the browser object, and the page object. But I'm being unable to use it when using the Page Object Model. Page members are undefined.
In every example that I've found I always seen the call to puppeteer and the browser and page object within the main function. I guess that's a problem with context access.
But it's possible to archieve what I'm looking for and still be able to use at least the page object within Page Object files ?
main.mjs
import Browser from './browser.mjs'
import HomePage from './HomePage.mjs'
async function main () {
const browser = new Browser();
const homePage = new HomePage(browser.page);
await homePage.open();
console.log(await homepage.getTitle());
await browser.close();
}
main();
browser.mjs
class Browser {
constructor() {
return this.main(); // I know, that's ugly
}
async main() {
this.browser = await puppeteer.launch({
headless: false,
});
this.page = await browser.newPage();
return this.browser,this.page
}
}
export default Browser
homepage.mjs
class HomePage {
constructor(page) {
this.page = page;
}
async open() {
this.page.goto('http://www.contoso.com');
}
async getTitle() {
return this.page.title();
}
}
export default HomePage
return this.browser,this.page
This returns the browser. If you want to access browser and page, you should return this only. Your code should look like below.
return this
You can access browser, page and everything between from that object.
Related
Assume this code
// puppeteer-manager.ts
import { Browser, executablePath, Page } from "puppeteer";
import puppeteerExtra from "puppeteer-extra";
import stealthPlugin from "puppeteer-extra-plugin-stealth";
puppeteerExtra.use(stealthPlugin());
interface PuppeteerInstance {
browser: Browser;
page: Page;
}
let instances: { [sessionId: string]: PuppeteerInstance } = {};
async function createPuppeteerInstance(
sessionId: string
): Promise<PuppeteerInstance> {
if (!instances[sessionId]) {
const browser = await puppeteerExtra.launch({
args: ["--no-sandbox"],
headless: false,
ignoreHTTPSErrors: true,
executablePath: executablePath(),
});
const page = await browser.newPage();
instances[sessionId] = { browser, page };
}
return instances[sessionId];
}
async function closePuppeteerInstance(sessionId: string): Promise<void> {
if (instances[sessionId]) {
const { browser } = instances[sessionId];
await browser.close();
delete instances[sessionId];
}
}
export { createPuppeteerInstance, closePuppeteerInstance };
So this code is responsible for creating new instances of puppeteer and storing them in a variable called instances and sessionId as the key.
So I'm building a NextJS app that let every connected user have their own puppeteer instance to achieve what the app is intended to.
So I will assume if I call createPuppeteerInstance in file A and pass it sessionId 1234 as session iD I will assume when I run createPuppeteerInstance in file B with the same sessionId I should get the same instance of puppeteer but for some reason, the variable instances in puppeteer-manager.ts not storing the different instance and createPuppeteerInstance keeps on creating a new instance every time I call it. I know this will just work in Express server so I'm asking/wondering could the problem be with NextJS that causes instances variable to go back to an empty object.
Below is the code for the test file
const { test, expect } = require('#playwright/test')
const HomePage = require('./pageobejcts/HomePage')
test.beforeEach(async ({ page }) => {
await page.goto("https://automationexercise.com/");
})
test('Navigate to Login', async ({ page }) => {
let homepage = new HomePage(page);
homepage.navigateToLoginSignUp();
await expect(page.locator('//div[#class="signup-form"]/h2')).toBeVisible();
await page.screenshot({ path: 'screenshot.png' });
//await expect(page.locator('//img[#alt="Website for automation practice"]')).toBeVisible;
})
And below is code for one of the Screen class
class HomePage {
constructor(page) {
this.page = page;
this.signupLnk = '//a[#href="/login"]';
}
async clickSignupLink() {
await this.page.click(this.signupLnk);
}
}
module.exports = HomePage;
Now the above code works fine. But in the Screen Class, as there is no Page object defined, there is no auto-complete/documentation available when using the Page methods.
eg On writing page.click(), the IDE does not recognize the click function and does not suggest the correct parameters
Is there a way to resolve this?
If I understand Your question correctly this should work:
let homepage = new HomePage(page);
await homepage.page.click('selector')
Also I suggest to use Fixtures in combination with page object model.
await this.signupLnk.click();
You can call the locator directly and call the click function on it.
add this to class file -
/**
#param {import('#playwright/test').Page} page
*/
So all I want to do is automate a login through WebdriverIO.
This is the LoginPage class where the error occurs, login function - "element not interactable"
class LoginPage {
get inputUsername() {
const username = $("#username");
username.waitForClickable();
return username;
}
get inputPassword() {
const password = $("#current-password");
password.waitForClickable();
return password;
}
get btnSubmit() {
const submitButton = $(".button.button--primary.button--l");
submitButton.waitForClickable();
return submitButton;
}
async login(username, password) {
await this.inputUsername.setValue(username); //<--Throws error
await this.inputPassword.setValue(password); //<--Throws error
await this.btnSubmit.click(); //<--Not sure if throws error
}
}
The parameters from the login function come from the call of it on the test file.
describe("login", () => {
it("should successfully login", async () => {
await browser.maximizeWindow();
await LoginPage.open(); //<--Opens the login page on the browser
await LoginPage.login(
"emailtest#test.com",
process.env.PASSWORD
);
await expect(browser).toHaveUrl(process.env.CLIENT_AREA_URL);
}
}
I believe I'm using mocha for the test structure and for the reporters (spec and allure), correct me if I'm wrong in something.
I'm kinda new in Automated Web Testing, can someone help me on this?
Try this:
class LoginPage {
get inputUsername() { return $("#username");
get inputPassword() {return $("#current-password");
get btnSubmit() {return $(".button.button--primary.button--l");
async login(username, password) {
await this.inputUsername.waitForClickable();
await this.inputUsername.setValue(username);
await this.password.waitForClickable();
await this.inputPassword.setValue(password);
await this.submitButton.waitForClickable();
await this.submitButton.click();
}
}
I would recommend to write even a method to fill the input:
async fillLoginName(loginname){
await this.inputUsername.waitForExist();
await this.inputUsername.clearValue();
await this.inputUsername.setValue(loginname);
}
and you called just in your test as:
it("should successfully login", async () => {
await Loginpage.fillLoginName("emailtest#test.com");
// do the rest for password field and click login button
)}
There are a couple things that could be causing this:
The page has a cookie "modal" window that pops up. This modal can block the text input into the login form, which is what the "element not interactable" message comes from. If this is the case, you'll also likely see an additional message saying another element received a click.
The login page loads content via JavaScript API calls. WebdriverIO/Selenium is good about waiting for normal page loads, but when you load content via JavaScript, it doesn't know to wait for that. You do have the username.waitForClickable(); calls, but I think they might need to be await username.waitForClickable();to work.
It looks like waitForClickable doesn't work in this case. Could you move it to the async function?
Based on your code:
get inputUsername() {
return $("#username");
}
async login(username, password) {
await this.inputUsername.waitForClickable();
await this.inputUsername.setValue(username);
...
}
wait... and set... can be moved to browser.addCommand() in wdio.conf to have a cleaner code.
Maybe i asked wrong question.
i'm trying to test some site, and have this throw in terminal
terminal output
it's obvious, that function returns uuid after it was called in code.
it work's normaly, when i take element right in code, so the reason, i think, in uncorrect import
here's my code :
file.js
describe('final homeTask', () => {
it('firstassigment', async () => {
let mainPage = require('../page/main.pageHW.js')
await browser.url('https://github.com/')
let signUpButton = await mainPage.topSignUpBtn()
await signUpButton.click()
})
})
main.pageHW.js
class MainPage {
get topSignUpBtn () { return $('a.btn-mktg:nth-child(1)') }
}
module.exports = new MainPage()
It is a problem with Javascript, not about webdriver, you are using a property in the MainPage class:
class MainPage {
/// The get word make it a property
get topSignUpBtn () { return $('a.btn-mktg:nth-child(1)') }
}
module.exports = new MainPage()
That means you DON'T need parentheses to use it, so, replace:
let signUpButton = await mainPage.topSignUpBtn()
By
let signUpButton = await mainPage.topSignUpBtn;
Also, the property doesn't need the await clause.
If you want more info you can check this link.
I have tried almost any example provided in docs but I can't run it.
I want to make a request to a specific url with axios (or fetch method) every 60 seconds and process the data in the background. In other words I want something common like:
this.getPageInterval = setInterval(() => {
const json = await (await fetch(this.fetchURL)).json();
...// etc
}, 60000)
can happen when the app is in background.
my console.log says 'task registered' but it feels like this block of code never triggers(global scope):
BackgroundFetch.setMinimumIntervalAsync(5);
const taskName = 'test-background-fetch';
TaskManager.defineTask(taskName, async () => {
console.log('background fetch running');
try {
const receivedNewData = await (await fetch(this.fetchUri)).json();
console.log('receivedNewData', receivedNewData)
return receivedNewData ? BackgroundFetch.Result.NewData : BackgroundFetch.Result.NoData;
let isRegistered = await TaskManager.isTaskRegisteredAsync(taskName);
console.log("isRegistered: ", isRegistered);
} catch (error) {
return BackgroundFetch.Result.Failed;
}
console.log("BackgroundFetch.Result.NewData", BackgroundFetch.Result.NewData);
return BackgroundFetch.Result.NewData;
});
and in my class component:
await BackgroundFetch.registerTaskAsync(taskName, {
setMinimumIntervalAsync: 5,
stopOnTerminate: false
});
await BackgroundFetch.setMinimumIntervalAsync(5);
alert('task registered');
console.log('task registered');
It seems you're not registering your task in your class component. Is taskname defined as 'test-background-fetch' in your component? You need to call exactly the same task name.
Also some phones are somewhat problematic with background tasks. I never tried it on expo, but on React Native some brands had to go through some extra setup.
Also note that in the documentation it says it only works when backgrounded. Terminated apps wont run the background function.