I'm trying to amend the DOM using a chrome extension. Unfortunately I fail with accessing the new page after some of the actions cause a page change. My code looks like this:
content_script.js
(async () => {
try {
execute_edit = async () => {
console.log(document.title)
const el = document.getElementsByClassName("icon")
const first_el = el[0]
first_el.click()
}
change_element = async () => {
console.log(document.title)
const el = document.querySelector("element")
console.log(el.innerHTML)
}
await execute_edit()
await change_element()
} catch (e) {
console.log(e)
}
})();
I'm getting the error that the "el" in "change_element" does not exist. This is obviously caused by the fact that both "document.title" are identical i.e. the second function still tries to access the original DOM and not the new page.
Any suggestions on how I can have the second function "change_element" access the new page's DOM?
Related
I am attempting to try click a button using code without an id or class, but my terminal always responds with:
document.getElementsByTagName("Accept Cookies");
^
ReferenceError: document is not defined
This is my code:
const puppeteer = require('puppeteer');
const product_url = "https://www.nike.com/launch"
async function givePage() {
const browser = await puppeteer.launch({headless: false});
const page = await browser.newPage();
return page;
}
async function acceptCookies(page) {
await page.goto(product_url);
const btn = await page.waitForSelector('#cookie-settings-layout > div > div > div >
div:nth-child(3) > div.ncss-col-md-6.ncss-col-sm-12.mb5-sm > button')
await btn.click()
}
async function notifyMe(page) {
await page.goto(product_url);
document.querySelector("button[type=\"submit\"]").click("Notify Me");
}
async function checkout() {
var page = await givePage();
await acceptCookies(page);
await notifyMe(page);
}
checkout();
What did I do wrong and how can I fix this?
There's no built-in variable in NodeJS named document, since it doesn't run in the browser.
If you want to access document, in Puppeteer there's a page.evaluate() function where you can access the document variable (as well as everything else inside client-side JS):
// ...
await page.evaluate(() => {
document.querySelector("button[type=\"submit\"]").click();
});
Please note though, that all the JavaScript you run will be run on the browser, not in NodeJS, so if you want to get the value back you can return:
const result = await page.evaluate(() => {
var something = document.getElementById("something");
return something.innerText;
});
console.log(result); // will print in the console "blah blah blah"
Likewise if you want to pass variables to the callback you have to give them to the evaluate function:
await page.evaluate((name, age) => {
// do something with 'name' and 'age'
}, "John", 34);
You already have an example on your code on how to access elements. Instead of document.querySelector, use page.waitForSelector like what you did on line 12.
document.querySelector('button[type="submit"]').click()
should be
(await page.waitForSelector('button[type="submit"]')).click()
In Nodejs, you don't have access to web APIs like a window, document, etc. so you can't use document.querySelector to select elements here.
Instead of handling clicks on DOM elements on server side, you should handle those clicks on the client-side only and then fetch the data from the server accordingly.
What I am trying to do is:
Load the page
Gain access to the contents of an external css named "mystyle.css"
Check if ".some_class" border has the value "2px"
I have tried
describe('CSS tests', () => {
it('.some_class border is 2px', async function () {
await page.goto(<homepageurl>);
const stylesheet = await page.evaluate(() => {
return document.querySelector("link[href*='mystyle.css']");
});
console.log(current_styles);
// rest of the code
});
});
I am getting an empty object {} as a result so I am lost and don't know how to carry on.
I am working on a project that creates a google chrome extension and I am using chrome API's in it. Now, I am trying to work my handleTabUpdate function when tab is updated. However, I am getting Unchecked runtime.lastError: No tab with id: 60
How can I fixed that? Here is my code:
chrome.tabs.onUpdated.addListener(handleTabUpdate)
function handleTabUpdate(tabId, info) {
if (info.status === 'loading') {
store.dispatch({ type: 'RESET_TABHOSTS' })
chrome.tabs.get(tabId, (activeTab) => {
if (tabId === store.getState().currentTab['id']) {
store.dispatch({ type: 'ACTIVE_TAB', payload: activeTab })
}
})
}
}
My guess is the tab you are looking for was closed, so when you try to get it by id the operation fails.
To avoid the error, my suggestion is to first query all tabs and see if a tab with a specific id exists in the result. If it does, run chrome.tabs.get() and with your logic.
Just bumped up against this issue in MV3 and I've tooled a solution that allows a bit more ease when working with tabs.
Functions
const handleRuntimeError = () => {
const error = chrome.runtime.lastError;
if (error) {
throw new Error(error);
}
};
const safeGetTab = async (tabId) => {
const tab = await chrome.tabs.get(parseInt(tabId));
try {
handleRuntimeError();
return tab;
} catch (e){
console.log('safeGetTab', e.message);
}
return {};
};
Implementation
(async () => {
// assumes some tabId
const tab = await safeGetTab(tabId);
})()
This will return a value no matter what. It will either return the tab object or an empty object. Then you can can just do some basic checking in your script to decide how you want to handle that. In my case I can simply ignore the action that would have been taken on that tab and move on.
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'm building a Chrome Extension to add some shortcut functionality to a site I regularly work with. I've tried calling my addTypingListeners() to bind the div with 2 inputs that I've added to the title and subtitle of the edit page I'm working on. However, I never seem to get into the document.eventListener closure.
My Chrome Extension is run at document_idle so the content should be loaded by the time my additional code runs. How can I get these listeners to embed on the page?
Even when I don't call addTypingListeners(), I still see a and b log in the console
function addTypingListeners() {
console.log('a')
var meta = {}
document.addEventListener("DOMContentLoaded",()=>{
console.log('listeners added pre')
bind(meta, document.getElementsByTagName('title'), "title");
bind(meta, document.getElementsByTagName('subtitle'), "subtitle");
setInterval(()=>{document.getElementsByTagName('h3')[0].innerText=meta.title});
setInterval(()=>{
console.log(meta)
document.getElementsByTagName('h4')[0].innerText = meta.subtitle
});
console.log('listeners added')
})
console.log('b')
}
const start = async function() {
// var location = window.location.toString()
let slug = window.location.toString().split("/")[4]
let url = `https://example.org/${slug}?as=json`
const _ = await fetch(url)
.then(res => res.text())
.then(text => {
let obj = JSON.parse(text);
const { payload } = obj;
// Container
const root = document.getElementById('container');
var clippyContainer = document.createElement('div');
createShell(clippyContainer, name);
root.appendChild(clippyContainer);
// Inputs
const title = document.getElementsByTagName('h3')[0];
const subtitle = document.getElementsByTagName('h4')[0];
var inputDiv = document.createElement('div');
inputDiv.id = "input-div";
const titleInput = document.createElement('input');
titleInput.id = "title"
titleInput.value = title.innerText;
inputDiv.appendChild(titleInput);
const breaker = document.createElement("br")
inputDiv.appendChild(breaker);
const subtitleInput = document.createElement('input');
subtitleInput.id = "subtitle"
subtitleInput.value = subtitle.innerText;
inputDiv.appendChild(subtitleInput);
clippyContainer.appendChild(inputDiv);
inputDiv.appendChild(breaker);
// addTypingListeners() // tried here, also doesn't work
});
}
start()
.then( (_) => {
console.log('hi')
addTypingListeners()
console.log("done")
})
Probably the event DOMContentLoaded was already fired at the point of time when you set the listener. You can check that document.readyState equals to complete and execute the function without subscribing to the event if it already occurred. In the opposite case if the readyState is loading or interactive you should set the listener as it is currently done in the attached example.
The code you provided should be injected to the page as a content script (for details).
According to the official documentation, the order of events while a page is loading:
document_start > DOMContentLoaded > document_end > load > document_idle.
The difference between load and DOMContentLoaded events is explained here as
The load event is fired when the whole page has loaded, including all dependent resources such as stylesheets and images. This is in contrast to DOMContentLoaded, which is fired as soon as the page DOM has been loaded, without waiting for resources to finish loading.
Thus, you should add the listeners without waiting for the DOMContentLoaded event, which will never fire.
This is literally all the coded need besides whatever your doing to the dom.
Background.js
let slug = window.location.toString().split("/")[4]
let url = `https://example.org/${slug}?as=json`
fetch(url).then(res => res.text()).then((data) => {
chrome.tabs.sendMessage(tabId, {
message: data
});
})
Content.js
function addTypingListeners(data) {
// Update page dom
}
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.message) {
addTypingListeners(request.message);
}
});