For example, when accessing the following page, is it possible to retrieve the target variable (json) that is output within the source of the page?
<html>
<body>
<h1>test</h1>
</body>
<script>
var target = {
name: 'testdata',
};
</script>
</html>
Yes because it's in global scope:
let target = await page.evaluate(() => target)
Related
I am looking for a way to disable the popup message which shows when using window.showDirectoryPicker. This page is served from localhost via WebView2 component in a WPF app.
<!DOCTYPE html>
<html>
<body>
<div class="fl" style="margin: 0 0 2rem 0"><button id="addToFolder">Give access to folder</button></div>
</body>
</html>
<script>
let directory;
document.getElementById('addToFolder').addEventListener('click', async () => {
try {
directory = await window.showDirectoryPicker({
startIn: 'desktop'
});
for await (const entry of directory.values()) {
let newEl = document.createElement('div');
newEl.innerHTML = `<strong>${entry.name}</strong> - ${entry.kind}`;
document.getElementById('folder-info').append(newEl);
}
} catch (e) {
console.log(e);
}
});
</script>
Note that I already have full file system access via the WPF app, so its not a security concern.
My Html code has Button-tags that have same id "hoge".
If you get the selector from the Chrome Dev Tool, it will be the same for both "#hoge".
<html>
<body>
<button id="hoge">Hoge</button>
<div class="shadow">
#shadow-root (open)
<button id="hoge">Hoge</button>
</div>
</body>
</html>
I want to get element of button-tag in shadow dom with puppeteer.
But, my javascript code gets element of 1st button.
const element = page.waitForSelector("pierce/#hoge");
This is not what I want.
I'm guessing it's because you didn't specify a unique selector, but i don't know what is unique selector for puppeteer.
If you know how to solve this problem, please let me know.
Long story short
I work with puppeteer a lot and wanted this knowlegde to be in my bag. One way to select a shadow Element is by accessing the parent DOM Node's shadowRoot property. The answer is based on this article.
Accessing Shadow Root property
For your html example this does the trick:
const button = document.querySelector('.shadow').shadowRoot.querySelector('#hoge')
waiting
Waiting though is a little more complicated but can be acquired using page.waitForFunction().
Working Sandbox
I wrote this full working sandbox example on how to wait for a certain shadowRoot element.
index.html (located in same directory as app.js)
<html>
<head>
<script>
// attach shadowRoot after 6 seconds for emulating waiting..
setTimeout(() => {
const btn = document.getElementById('hoge')
const container = document.getElementsByClassName('shadow')[0]
const shadowRoot = container.attachShadow({
mode: 'open'
})
shadowRoot.innerHTML = `<button id="hoge" onClick="doStuff()">hoge2</button>`
console.log('attached!.')
}, 6000)
function doStuff() {
alert('shadow button clicked!')
}
</script>
</head>
<body>
<button id="hoge">Hoge</button>
<div class="shadow">
</div>
</body>
</html>
app.js (located in same directory as index.html)
var express = require('express')
var { join } = require('path')
var puppeteer = require('puppeteer')
//utility..
const wait = (seconds) => {
console.log('waiting', seconds, 'seconds')
return new Promise((res, rej) => {
setTimeout(res, seconds * 1000)
})
}
const runPuppeteer = async() => {
const browser = await puppeteer.launch({
defaultViewport: null,
headless: false
})
const page = await browser.newPage()
await page.goto('http://127.0.0.1:5000')
await wait(3)
console.log('page opened..')
// only execute this function within a page context!.
// for example in page.evaluate() OR page.waitForFunction etc.
// don't forget to pass the selector args to the page context function!
const selectShadowElement = (containerSelector, elementSelector) => {
try {
// get the container
const container = document.querySelector(containerSelector)
// Here's the important part, select the shadow by the parentnode of the
// actual shadow root and search within the shadowroot which is like another DOM!,
return container.shadowRoot.querySelector(elementSelector)
} catch (err) {
return null
}
}
console.log('waiting for shadow elemetn now.')
const containerSelector = '.shadow'
const elementSelector = '#hoge'
const result = await page.waitForFunction(selectShadowElement, { timeout: 15 * 1000 }, containerSelector, elementSelector)
if (!result) {
console.error('Shadow element not found..')
return
}
// since waiting succeeded we can get the elemtn now.
const element = await page.evaluateHandle(selectShadowElement, containerSelector, elementSelector)
try {
// click the element.
await element.click()
console.log('clicked')
} catch (err) {
console.log('failed to click..')
}
await wait(10)
}
var app = express()
app.get('/', (req, res) => {
res.sendFile(join(__dirname, 'index.html'))
})
app.listen(5000, '127.0.0.1', () => {
console.log('listening!')
runPuppeteer()
})
Start example
$ npm i express puppeteer
$ node app.js
Make sure to use headless:false option to see what's happening.
The application does this:
start a small express server only serving index.html on /
open puppeteer after server has started and wait for the shadow root element to appear.
Once it appeared, it gets clicked and an alert() is shown. => success!
Browser Support
Tested with chrome.
Cheers ' ^^
I want to print a pdf file with an html image embedded on it in react native expo mobile application. And when I tried generating the pdf file, image is not included on the generated pdf file. Any help on how to include image in my pdf file.
createPDF = async (html) => {
try {
const {uri} = await Print.printToFileAsync(html);
Print.printAsync({ uri });
this.setState({callPrint: false});
} catch(err) {
console.error(err);
this.setState({callPrint: false});
}
};
const html = "
<html>
<body>
<div class='title-container'>
<img source="asset/omnix.png" />
</div>
</body>
</html>
";
This is a known problem on iOS for expo, adding a fetchImageData function to convert the image to a Base64 string is the recommended fix
createPDF = async (html) => {...};
fetchImageData = (uri) => { // fetch Base64 string of image data
const data = await FileSystem.readAsStringAsync('file://' + uri, {
encoding: FileSystem.EncodingType.Base64,
});
return imageData = 'data:image/png;base64,' + data;
};
const html = "
<html>
<body>
<div class='title-container'>
<img source=${fetchImageData('asset/omnix.png')} />
</div>
</body>
</html>";
If you use fetchImageData to fill all your images they will print correctly
I have been following a video tutorial which apparently using JSBin to show its code, when I tried out the code locally then it does not work for me. Could someone please help me to figure out what is the issue.
Below is the code
<!DOCTYPE html>
<html>
<head>
<title>Redux basic example</title>
<script src="https://unpkg.com/redux#latest/dist/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.0/react.min.js" type = "text/babel"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.6.0/react-dom.min.js" type = "text/babel"></script>
</head>
<body>
<div id='root'>
</div>
<script>
const counter = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
};
const Counter = ({ value}) => (<div>{value}</div>);
const { createStore } = Redux;
var store = createStore(counter);
const render = () => {
ReactDOM.render(
<Counter value={store.getState()} onIncrement = {
() => store.dispatch({type: 'INCREMENT'})
}
onDecrement = {
() => store.dispatch({type: 'DECREMENT'})
} />,
document.getElementById('root')
);
};
store.subscribe(render);
render();
</script>
</body>
</html>
You are using JSX in your code, which needs to be transpiled into standard javascript before executing it in the browser.
const Counter = ({ value}) => (<div>{value}</div>);
Look into Babel
The browser is complaining about the JSX code. You should transpile it to regular Javascript before including it in your page. There are several ways to do: Webpack, Babel...
Have a look to create-react-app npm package to get started fast: https://github.com/facebookincubator/create-react-app
I have been trying to render the json data to the view by calling
the rest api and the code is as follows:
var Profile = Backbone.Model.extend({
dataType:'jsonp',
defaults: {
intuitId: null,
email: null,
type: null
},
});
var ProfileList = Backbone.Collection.extend({
model: Profile,
url: '/v1/entities/6414256167329108895'
});
var ProfileView = Backbone.View.extend({
el: "#profiles",
template: _.template($('#profileTemplate').html()),
render: function() {
_.each(this.model.models, function(profile) {
var profileTemplate = this.template(this.model.toJSON());
$(this.el).append(tprofileTemplate);
}, this);
return this;
}
});
var profiles = new ProfileList();
var profilesView = new ProfileView({model: profiles});
profiles.fetch();
profilesView.render();
and the html file is as follows:
<!DOCTYPE html>
<html>
<head>
<title>SPA Example</title>
<!--
<link rel="stylesheet" type="text/css" href="src/css/reset.css" />
<link rel="stylesheet" type="text/css" href="src/css/harmony_compiled.css" />
-->
</head>
<body class="harmony">
<header>
<div class="title">SPA Example</div>
</header>
<div id="profiles"></div>
<script id="profileTemplate" type="text/template">
<div class="profile">
<div class="info">
<div class="intuitId">
<%= intuitId %>
</div>
<div class="email">
<%= email %>
</div>
<div class="type">
<%= type %>
</div>
</div>
</div>
</script>
</body>
</html>
This gives me an error and the render function isn't invoking
properly and the render function is called even before the REST
API returns the JSON response.
Could anyone please help me to figure out where I went wrong. Any help is highly appreciated
Thank you
Firstly, you need to pass the model attributes to the template function explicitly. So change the appropriate code in the view to:
var ProfileView = Backbone.View.extend({
el: "#profiles",
//template: _.template($('#profileTemplate').html()), REMOVED
render: function() {
_.each(this.model.models, function(profile) {
var profileTemplate = _.template($('#profileTemplate').html(), {
intuitId: profile.get("intuitId"),
email: profile.get("email"),
type: profile.get("type")
});
$(this.el).append(tprofileTemplate);
}, this);
return this;
}
});
Secondly, your render method is not dependent on the fetch response from being returned from the server. It will get called immediately after the line above it executes and not wait for the fetch response. This behavior you are experiencing is by design. If you want to call render after you get the response back from the server you'll have to use events. You could replace profilesView.render(); with something like:
profilesView.listenTo(profiles, "sync", profilesView.render);
This means that the profilesView will listen for the profiles collection to complete its fetch and fire a sync event. When this occurs, the render function of the view will be called.