├── tests
├── server
│ ├── empty.html
│ ├── empty2.html
│ ├── form.html
│ ├── worker.html
│ └── worker.js
├── hello.rs
├── selectors
│ └── mod.rs
├── browser_type
│ └── mod.rs
├── browser
│ └── mod.rs
├── devices
│ └── mod.rs
├── connect
│ └── mod.rs
├── test.rs
└── browser_context
│ └── mod.rs
├── .gitignore
├── .rustfmt.toml
├── src
├── imp
│ ├── dialog.rs
│ ├── binding_call.rs
│ ├── video.rs
│ ├── file_hooser.rs
│ ├── download.rs
│ ├── stream.rs
│ ├── console_message.rs
│ ├── selectors.rs
│ ├── core
│ │ ├── driver.rs
│ │ ├── transport.rs
│ │ ├── event_emitter.rs
│ │ ├── message.rs
│ │ └── connection.rs
│ ├── artifact.rs
│ ├── route.rs
│ ├── response.rs
│ ├── js_handle.rs
│ ├── websocket.rs
│ ├── worker.rs
│ ├── browser.rs
│ ├── request.rs
│ ├── playwright.rs
│ ├── utils.rs
│ └── browser_context.rs
├── api
│ ├── video.rs
│ ├── file_chooser.rs
│ ├── websocket.rs
│ ├── console_message.rs
│ ├── dialog.rs
│ ├── response.rs
│ ├── worker.rs
│ ├── selectors.rs
│ ├── download.rs
│ ├── js_handle.rs
│ ├── accessibility.rs
│ ├── playwright.rs
│ ├── request.rs
│ ├── route.rs
│ ├── browser.rs
│ ├── input_device.rs
│ └── browser_context.rs
├── main.rs
├── lib.rs
├── api.rs
├── imp.rs
└── build.rs
├── scripts
├── Makefile
├── Cargo.toml
└── src
│ └── diff.rs
├── Makefile
├── .github
└── workflows
│ └── ci.yml
├── Cargo.toml
└── README.md
/tests/server/empty.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/server/empty2.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | Cargo.lock
3 | driver
4 | tarpaulin-report.html
5 |
--------------------------------------------------------------------------------
/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | edition = "2018"
2 | unstable_features = true
3 | fn_single_line = true
4 | trailing_comma = "Never"
5 | imports_granularity = "Crate"
6 | normalize_comments = true
7 | normalize_doc_attributes = true
8 |
--------------------------------------------------------------------------------
/tests/server/form.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/server/worker.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/imp/dialog.rs:
--------------------------------------------------------------------------------
1 | use crate::imp::core::*;
2 |
3 | #[derive(Debug)]
4 | pub(crate) struct Dialog {
5 | channel: ChannelOwner
6 | }
7 |
8 | impl Dialog {
9 | pub(crate) fn new(channel: ChannelOwner) -> Self { Self { channel } }
10 | }
11 |
12 | impl RemoteObject for Dialog {
13 | fn channel(&self) -> &ChannelOwner { &self.channel }
14 | fn channel_mut(&mut self) -> &mut ChannelOwner { &mut self.channel }
15 | }
16 |
--------------------------------------------------------------------------------
/tests/server/worker.js:
--------------------------------------------------------------------------------
1 | console.log('hello from the worker');
2 |
3 | function workerFunction() {
4 | return 'worker function result';
5 | }
6 |
7 | self.addEventListener('message', event => {
8 | console.log('got this data: ' + event.data);
9 | });
10 |
11 | (async function() {
12 | while (true) {
13 | self.postMessage(workerFunction.toString());
14 | await new Promise(x => setTimeout(x, 100));
15 | }
16 | })();
17 |
--------------------------------------------------------------------------------
/src/imp/binding_call.rs:
--------------------------------------------------------------------------------
1 | use crate::imp::core::*;
2 |
3 | #[derive(Debug)]
4 | pub(crate) struct BindingCall {
5 | channel: ChannelOwner
6 | }
7 |
8 | impl BindingCall {
9 | pub(crate) fn new(channel: ChannelOwner) -> Self { Self { channel } }
10 | }
11 |
12 | impl RemoteObject for BindingCall {
13 | fn channel(&self) -> &ChannelOwner { &self.channel }
14 | fn channel_mut(&mut self) -> &mut ChannelOwner { &mut self.channel }
15 | }
16 |
--------------------------------------------------------------------------------
/scripts/Makefile:
--------------------------------------------------------------------------------
1 | ../src/api/generated.rs: ../src/api/api.json
2 | rustfmt --emit stdout <(cargo run --bin gen < $^)| tail +3 > $@
3 | sed -i 's/\[actionability\](.\/actionability.md)/[actionability](https:\/\/playwright.dev\/docs\/actionability\/)/g' $@
4 |
5 | ../src/api/api.json: ../src/build.rs
6 | cd .. && cargo run print-api-json |jq > src/api/api.json
7 |
8 | diff:
9 | cargo run --bin diff <(git show master:src/api/api.json) <(git show HEAD:src/api/api.json)
10 |
--------------------------------------------------------------------------------
/scripts/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "scripts"
3 | version = "0.1.0"
4 | edition = "2018"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | anyhow = "1.0.41"
10 | case = "1.0.0"
11 | proc-macro2 = "1.0.27"
12 | quote = "1.0.9"
13 | serde = { version = "1.0.126", features = ["derive"] }
14 | serde_json = "1.0.64"
15 |
16 | [[bin]]
17 | name = "gen"
18 | path = "src/gen.rs"
19 |
20 | [[bin]]
21 | name = "diff"
22 | path = "src/diff.rs"
23 |
--------------------------------------------------------------------------------
/src/api/video.rs:
--------------------------------------------------------------------------------
1 | use crate::imp::{core::*, prelude::*, video::Video as Impl};
2 |
3 | #[derive(Debug, Clone)]
4 | pub struct Video {
5 | inner: Impl
6 | }
7 |
8 | impl Video {
9 | pub(crate) fn new(inner: Impl) -> Self { Self { inner } }
10 |
11 | pub fn path(&self) -> Result { self.inner.path() }
12 |
13 | // doesn't work with this version
14 | async fn save_as>(&self, path: P) -> ArcResult<()> {
15 | self.inner.save_as(path).await
16 | }
17 |
18 | // doesn't work with this version
19 | async fn delete(&self) -> ArcResult<()> { self.inner.delete().await }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | use playwright::*;
2 | use std::{env, io, process};
3 |
4 | fn main() {
5 | let envs = env::vars_os();
6 | let args = {
7 | let mut a = env::args_os();
8 | a.next();
9 | a
10 | };
11 | let status = run(args, envs).unwrap();
12 | if let Some(status) = status.code() {
13 | std::process::exit(status)
14 | }
15 | }
16 |
17 | fn run(args: env::ArgsOs, envs: env::VarsOs) -> io::Result {
18 | let driver = Driver::install().unwrap();
19 | process::Command::new(driver.executable())
20 | .args(args)
21 | .envs(envs)
22 | .status()
23 | }
24 |
--------------------------------------------------------------------------------
/src/imp/video.rs:
--------------------------------------------------------------------------------
1 | use crate::imp::{artifact::Artifact, core::*, prelude::*};
2 |
3 | #[derive(Debug, Clone)]
4 | pub(crate) struct Video {
5 | artifact: Weak
6 | }
7 |
8 | impl Video {
9 | pub(crate) fn new(artifact: Weak) -> Self { Self { artifact } }
10 |
11 | pub(crate) fn path(&self) -> Result {
12 | Ok(upgrade(&self.artifact)?.absolute_path.as_str().into())
13 | }
14 |
15 | pub(crate) async fn save_as>(&self, path: P) -> ArcResult<()> {
16 | upgrade(&self.artifact)?.save_as(path).await
17 | }
18 |
19 | pub(crate) async fn delete(&self) -> ArcResult<()> { upgrade(&self.artifact)?.delete().await }
20 | }
21 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | all: format lint test doc
2 | cargo build --release
3 |
4 | dev:
5 | cargo fmt
6 | cargo clippy --all-targets
7 | cargo test
8 | cargo doc
9 |
10 | d:
11 | cargo watch -c -s 'make dev'
12 |
13 | format:
14 | cargo fmt
15 |
16 | lint:
17 | cargo clippy --all-targets
18 | cargo clippy --no-default-features --features chrono --features rt-actix --all-targets
19 | cargo clippy --no-default-features --features chrono --features rt-async-std --all-targets
20 |
21 | test:
22 | cargo test hello
23 | cargo test --all-targets
24 | cargo test --no-default-features --features chrono --features rt-actix --all-targets
25 | cargo test --no-default-features --features chrono --features rt-async-std --all-targets
26 |
27 | doc:
28 | cargo doc
29 |
30 | cov:
31 | cargo tarpaulin --out html --exclude-files scripts/ tests/ src/build.rs src/main.rs src/generated.rs
32 |
--------------------------------------------------------------------------------
/src/imp/file_hooser.rs:
--------------------------------------------------------------------------------
1 | use crate::imp::{
2 | core::*, element_handle::ElementHandle as ElementHandleImpl, page::Page as PageImpl, prelude::*
3 | };
4 |
5 | /// `FileChooser` objects are dispatched by the page in the [page::Event::FileChooser](crate::api::page::Event::FileChooser) event.
6 | ///
7 | /// ```js
8 | /// const [fileChooser] = await Promise.all([
9 | /// page.waitForEvent('filechooser'),
10 | /// page.click('upload')
11 | /// ]);
12 | /// await fileChooser.setFiles('myfile.pdf');
13 | /// ```
14 | #[derive(Debug, Clone)]
15 | pub struct FileChooser {
16 | pub(crate) page: Weak,
17 | pub(crate) element_handle: Weak,
18 | pub(crate) is_multiple: bool
19 | }
20 |
21 | impl FileChooser {
22 | pub(crate) fn new(
23 | page: Weak,
24 | element_handle: Weak,
25 | is_multiple: bool
26 | ) -> Self {
27 | Self {
28 | page,
29 | element_handle,
30 | is_multiple
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/api/file_chooser.rs:
--------------------------------------------------------------------------------
1 | pub use crate::imp::file_hooser::FileChooser;
2 | use crate::{
3 | api::{element_handle::SetInputFilesBuilder, ElementHandle, Page},
4 | imp::utils::File
5 | };
6 |
7 | impl FileChooser {
8 | /// Returns input element associated with this file chooser.
9 | fn element(&self) -> ElementHandle { ElementHandle::new(self.element_handle.clone()) }
10 | /// Returns whether this file chooser accepts multiple files.
11 | fn is_multiple(&self) -> bool { self.is_multiple }
12 | /// Returns page this file chooser belongs to.
13 | fn page(&self) -> Page { Page::new(self.page.clone()) }
14 |
15 | /// Sets the value of the file input this chooser is associated with. If some of the `filePaths` are relative paths, then
16 | /// they are resolved relative to the the current working directory. For empty array, clears the selected files.
17 | fn set_input_files_builder(&self, file: File) -> SetInputFilesBuilder {
18 | SetInputFilesBuilder::new(self.element_handle.clone(), file)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #[macro_use]
2 | extern crate serde;
3 | #[macro_use]
4 | extern crate serde_with;
5 |
6 | pub mod api;
7 | mod imp;
8 |
9 | pub use crate::imp::core::{Driver, Error};
10 | pub use api::playwright::Playwright;
11 |
12 | #[doc(hidden)]
13 | #[macro_export]
14 | macro_rules! runtime_test {
15 | ($name:tt, $main:stmt) => {
16 | #[cfg(feature = "rt-tokio")]
17 | #[test]
18 | fn $name() {
19 | env_logger::builder().is_test(true).try_init().ok();
20 | tokio::runtime::Builder::new_current_thread()
21 | .enable_all()
22 | .build()
23 | .unwrap()
24 | .block_on(async { $main });
25 | }
26 |
27 | #[cfg(feature = "rt-actix")]
28 | #[test]
29 | fn $name() {
30 | env_logger::builder().is_test(true).try_init().ok();
31 | actix_rt::System::new().block_on(async { $main });
32 | }
33 |
34 | #[cfg(feature = "rt-async-std")]
35 | #[test]
36 | fn $name() {
37 | env_logger::builder().is_test(true).try_init().ok();
38 | async_std::task::block_on(async { $main });
39 | }
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/src/imp/download.rs:
--------------------------------------------------------------------------------
1 | use crate::imp::{artifact::Artifact, core::*, prelude::*};
2 |
3 | #[derive(Debug)]
4 | pub(crate) struct Download {
5 | url: String,
6 | suggested_filename: String,
7 | artifact: Weak
8 | }
9 |
10 | impl Download {
11 | pub(crate) fn new(artifact: Weak, url: String, suggested_filename: String) -> Self {
12 | Self {
13 | url,
14 | suggested_filename,
15 | artifact
16 | }
17 | }
18 |
19 | pub(crate) fn url(&self) -> &str { &self.url }
20 |
21 | pub(crate) fn suggested_filename(&self) -> &str { &self.suggested_filename }
22 |
23 | pub(crate) async fn path(&self) -> ArcResult