How to handle shadow DOM inside iframe with Puppeteer - javascript

I want to click a button in a shadow DOM inside an iframe. Is there a way to do this?
<html>
<head></head>
<body>
<iframe class="iframe_1">
#document
<div class="shadow-root">
#shadow-root (open)
<div>
<button id="btn_1"></button>
<button id="btn_2"></button>
</div>
</iframe>
</body>
</body>
I did this:
const frameHandle = await page.$("iframe.iframe_1");
const frame = await frameHandle.contentFrame();
var button = await frame.querySelector(".shadow-root").shadowRoot.querySelector("button[id='btn_1']");
await button.click();
But got the following error:
Uncaught TypeError TypeError: frame.querySelector is not a function
I know why this error has occurred, but I can't come up with other ideas. Please teach me.

The error is telling you that you're trying to call frame.querySelector, but querySelector is not a function that exists on frame. DOMElement#querySelector is defined in the browser only, which you can access via a frame using an evaluate-family call.
For example, if you want to return an element, you can use evaluateHandle:
const button = await frame.evaluateHandle(() =>
document.querySelector(".shadow-root")
.shadowRoot
.querySelector("#btn_1")
);
Here's a minimal, complete example you can run and see in action on your provided DOM structure:
index.html:
<!DOCTYPE html>
<html><body>
<iframe class="iframe_1"></iframe>
<script>
const html = `<!DOCTYPE html>
<div class="shadow-root"></div>
<script>
const el = document.querySelector(".shadow-root");
const root = el.attachShadow({mode: "open"});
el.shadowRoot.innerHTML = \`
<button id="btn_1">A</button>
<button id="btn_2">B</button>
\`;
el.shadowRoot.querySelector("#btn_1").addEventListener("click", event => {
event.target.textContent = "clicked";
});
<\/script>
`;
const doc = document.querySelector("iframe").contentWindow.document;
doc.open();
doc.write(html);
doc.close();
</script>
</body></html>
index.js:
const fs = require("node:fs/promises");
const puppeteer = require("puppeteer"); // ^19.6.3
let browser;
(async () => {
browser = await puppeteer.launch();
const [page] = await browser.pages();
const html = (await fs.readFile("index.html")).toString();
await page.setContent(html);
const frameHandle = await page.$("iframe.iframe_1");
const frame = await frameHandle.contentFrame();
const button = await frame.evaluateHandle(() =>
document.querySelector(".shadow-root")
.shadowRoot
.querySelector("#btn_1")
);
await button.click();
const result = await button.evaluate(el => el.textContent);
console.log(result); // => clicked
})()
.catch(err => console.error(err))
.finally(() => browser?.close());

Related

fetching data from json not showing on browser

this functionality is new to me, im building this with the help of a tutorial but since the tutorial fetched a different set of data i got lost.
const puppeteer = require('puppeteer');
const fs = require('fs');
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://br.tradingview.com/symbols/TVC-DXY/',{
waitUntill: 'load',
timeout: 0
});
/*const element = await page.$(".tv-symbol-header__first-line");
const text = await page.evaluate(element => element.textContent, element);*/
const textNode = await page.evaluate(()=>{
const nodeText = document.querySelector(".tv-symbol-price-quote__change-value").innerText;
const text = [nodeText];
return text
});
fs.writeFile('arreglo.json', JSON.stringify(textNode), err =>{
if (err) throw new Error ('algo deu errado')
console.log('deu certo')
})
//await browser.close();
})();
<script>
(async() => {
const response = await fetch('./arreglo.json')
const data = await response.json();
document.querySelector(".container").innerHTML = data
})();
</script>
the first part is index.js
the second piece of code is in the html script.
the file arreglo.json is created and the result is like this:
["+0.887"]
i just want the 0.887, but i could format, not a problem, but i cant seem to get it on the html page.
It would be helpful to see the rest of your HTML code, or at least where you have placed the script tag as you have used a querySelector as part of the fetch request to render the data. Javascript code runs in order so if your script tag is before the ".container" element then your script will run before the ".container" element is available to render the data.
If you haven't already then I would place your script tags at the bottom of your HTML page so that all static content is rendered before any scripts start to amend it:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div class="container">
Content here...
</div>
<script>
(async() => {
const response = await fetch('./arreglo.json')
const data = await response.json();
document.querySelector(".container").innerHTML = data
})();
</script>
</body>
</html>

How do i take input from user and search api?

How can I take input (page number) from the user and then look for the corresponding text that has that page number and display it for the user using this API = https://api.alquran.cloud/v1/page/${number}/quran-uthmani
If I clearly understand you, then you need this:
async function getVersesforpage(number) { //to get verses for a certain pagetry
try {
const url = `https://api.alquran.cloud/v1/page/${number}/quran-uthmani`;
const response = await fetch(url);
const data = await response.json();
const ayahs = data.data.ayahs;
const verses = ayahs.map(ayah => ayah.text);
return verses;
} catch (e) {
console.log("Error:", e.message)
}
}
const form = document.getElementById("form");
form.addEventListener("submit", async(event) => {
event.preventDefault();
const {
number
} = event.target.elements;
const value = number.value;
const verses = await getVersesforpage(value);
console.log(verses);
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>بصائر</title>
<link href="css/stylesheet1.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link href="css/img/logo1.png" rel="icon">
<script type="module"></script>
</head>
<body>
<header class="header">
<form id="form">
<label for="number">رقم الصفحة</label>
<input id="number" placeholder="Search.." type="number">
<button class="fa fa-search" type="submit"></button>
</form>
</header>
<!--<script src="https://unpkg.com/localbase/dist/localbase.dev.js"></script>-->
<script src="js/main.js"></script>
</body>
</html>
You can get the user value from any field by filed id using document.getElementById('idName').value.
I prefered to edit you code a bit now the below code returns the result from API.
async function getVersesforpage(pageNumber) {//to get verses for a certain pagetry
try {
const url = `https://api.alquran.cloud/v1/page/${pageNumber}/quran-uthmani`;
const response = await fetch(url);
const verses = await response.json();
return verses;
}
catch (e) {
console.log("Error:", e.message)
}
}
document.getElementById("form").addEventListener("submit", async (event) => {
event.preventDefault();
const pageNumber = document.getElementById('number').value;
const verses = await getVersesforpage(pageNumber);
console.log(verses.data);
});

Testing welcome paragraph to equal to "welcome" instead of empty string

I am using jest, jsdom and testing-libray/dom to test this page (index.html). i expect paragraph to be "welcome" but i am getting empty string ("") because a page (index.html) is loading before javascript.
I would like to ask if there are other ways to do it. thanks.
this is how my code looks like.
index.html
<html>
<body>
<p data-testid="paragraph"></p>
<script type="text/javascript">
const p = document.querySelector("p");
p.textContent = "welcome";
</script>
</body>
</html>
index.test.js
const { getByTestId } = require("#testing-library/dom");
const { JSDOM } = require("jsdom");
require("#testing-library/jest-dom/extend-expect");
const path = require("path");
const fs = require("fs");
let dom;
let container;
const html = fs.readFileSync(path.join(__dirname, "./index.html"));
describe("index", () => {
beforeEach(() => {
dom = new JSDOM(html, { runScripts: "dangerously" });
container = dom.window.document.body;
});
it("should display welcome text", () => {
const pEl = getByTestId(container, "paragraph");
expect(pEl.textContent).toEqual("welcome");
});
});
screenshoot
enter image description here
thanks

I have an error with PDF.JS with the global worker and the async

I have an issue with my script on pdfjs.
i try a first time and i have this message :
Deprecated API usage: No "GlobalWorkerOptions.workerSrc" specified.
uncaught exception: undefined
so i follow the docs on github : https://github.com/mozilla/pdf.js/issues/10478
and now i have this message : SyntaxError: await is only valid in async functions and async generators
<div class="d-flex justify-content-center">
<canvas id="pdf-render">
</canvas>
</div>
<div class="pdfcontroller d-flex justify-content-around">
<button class="btn btn-primary" id="prev-page">
prev page
</button>
<p class="page-info"> 1 / 2</p>
<button class="btn btn-primary" id="next-page">
Next page
</button>
</div>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.4.456/pdf.min.js">
</script>
<script>
const url = 'filesample.pdf'
let pdfDoc = null;
pageNum = 1;
pageIsrendreing = false;
pageNumisPending = null;
const scale = 1.5,
canvas = document.querySelector('#pdf-render'),
ctx = canvas.getContext('2d');
// Render the page
const renderPage = num =>{
}
// Get the doc
const pdfjs = await import('pdfjs-dist/build/pdf');
const pdfjsWorker = await import('pdfjs-dist/build/pdf.worker.entry');
pdfjs.GlobalWorkerOptions.workerSrc = pdfjsWorker;
pdfjsLib.getDocument(url).promise.then(pdfDoc_ => {
pdfDoc = pdfDoc_;
console.log(pdfDoc)
});
</script>
That doesn't have anything to do with PDF.js. As it states, you can't use the async await syntax outside of an async function. Just put everything inside a function e.g. async function init() {} and call it when the window is loaded.

Getting the sibling of an elementHandle in Puppeteer

I'm doing
const last = await page.$('.item:last-child')
Now I'd love to get the preceding element based on last. ie
const prev = last.$.prev()
Any thoughts on how to do this?
Thanks!
You should use previousElementSibling inside evaluateHandle, like this:
const prev = await page.evaluateHandle(el => el.previousElementSibling, last);
Here is full example:
const puppeteer = require('puppeteer');
const html = `
<html>
<head></head>
<body>
<div>
<div class="item">item 1</div>
<div class="item">item 2</div>
<div class="item">item 3</div>
</div>
</body>
</html>`;
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto(`data:text/html,${html}`);
const last = await page.$('.item:last-child');
const prev = await page.evaluateHandle(el => el.previousElementSibling, last);
console.log(await (await last.getProperty('innerHTML')).jsonValue()); // item 3
console.log(await (await prev.getProperty('innerHTML')).jsonValue()); // item 2
await browser.close();
})();

Categories