Skip to content

Add Print page support in Java #8991

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Jan 19, 2021
Merged

Conversation

raju249
Copy link
Member

@raju249 raju249 commented Dec 22, 2020

Thanks for contributing to Selenium!
A PR well described will help maintainers to quickly review and merge it

Before submitting your PR, please check our contributing guidelines.
Avoid large PRs, help reviewers by making them as simple and short as possible.

Description

Motivation and Context

Fixes #8802

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist

  • I have read the contributing document.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

@raju249 raju249 added the C-java Java Bindings label Dec 22, 2020
@raju249 raju249 self-assigned this Dec 22, 2020
@raju249 raju249 changed the title Add PrintOptions class and supporting classes for page margin and size Add Print page support in Java Dec 23, 2020
@raju249 raju249 marked this pull request as ready for review January 5, 2021 05:45
@luke-hill
Copy link
Contributor

Just as an FYI (Code aside). If you notice any areas where the API in java is vastly different or needs a lot more work than Ruby, it's probably work flagging it at first instance, as I know it would be good to get these looking reasonably similar.

// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.printoptions;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/printoptions/print/

}

public void setTop(double top) {
if (top < 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use Require.positive

}

public double getTop() {
return this.top;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't use this unless it's strictly necessary or (often) in a constructor. Our IDE will let us know that this is a field.

this.left = left;
}

public Map<String, Double> toJson() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this method. The default serialisation mechanism will create an object of this shape by default.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition, please don't expose the json methods as public. They're not part of the user-facing API of Selenium

this.width = width;
}

public Map<String, Double> toJson() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not public and given how our json serialiser works, is not necessary.

@@ -331,6 +332,11 @@ public WebElement findElement(By locator) {
}
}

public String printPage(PrintOptions options) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is something we expect all WebDriver instances to support (and we do, since it's in the spec) this could be "somewhere" on WebDriver and not only on RemoteWebDriver. However, we try and keep the API of the main interfaces as compact as possible, and use role-based interfaces where necessary (which is why there are so many of them --- even for JS execution)

Printing a page feels very similar to taking a screenshot, so we have two options to make this "feel" like a webdriver API. The first is to add a new method to TakesScreenshot (a default one that throws an exception, to keep our promise of Se4 being a drop-in replacement) The second is to add a new role-based interfaces (eg. PrintsPage) I guess a final option is to add a print method to WebDriver.Window, but that doesn't feel quite right.

Finally, the return type of this method tells us nothing. It's fine to use a tiny type for this kind of thing. In TakesScreenshot we use an OutputType. If we could munge things to and from PDF without additional libraries, I'd suggest adding PDF to OutputType and changing the print signature to be print(OutputType<X> as), but that's not an option I'm entirely comfortable with as PDFBox adds about 4MB to the size of Selenium. As such, I'd recommend declaring a Pdf class and returning that.

import org.openqa.selenium.printoptions.PrintOptions;
import org.openqa.selenium.testing.JUnit4TestBase;

public class ChromePrintCommandTest extends JUnit4TestBase {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this chrome only? We expect Edge, Firefox and Safari to all support this functionality.

private static String MAGIC_STRING = "JVBER";

@Before
public void setUp() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once you've added a role-based interface, the thing to do here would be to Assume that the driver implements the interface. If it does, cast to it and store on a field.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also suggest navigating to a page: some browsers are in weird states when they start

driver = new ChromeDriver(options);
driver.get(pages.printPage);
}
@After
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: empty line before @After to give the methods some room to breathe. If you follow the advice above, this isn't needed.

import org.openqa.selenium.printoptions.PrintOptions;
import org.openqa.selenium.testing.JUnit4TestBase;

public class FireFoxPrintCommandTest extends JUnit4TestBase {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

o_O Why is this not the same as the first test?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chrome needs to be headless, if not it throws an exception. Firefox doesn't need to headless.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs to be lifted up to the main test suite, and this class removed. If chrome can't handle this when not headless we should file a bug with the chrome team, ignore the test for chrome, and link to that bug in the ignore's comment.

@raju249
Copy link
Member Author

raju249 commented Jan 5, 2021

Just as an FYI (Code aside). If you notice any areas where the API in java is vastly different or needs a lot more work than Ruby, it's probably work flagging it at first instance, as I know it would be good to get these looking reasonably similar.

Hey @luke-hill 👋
Yes, I understand your point. @AutomatedTester and I had a discussion about it while I was implementing for Ruby. I did the same before for ruby, but @barancev had a different opinion on that code, so I had to change it to as per their suggestions.

Regarding Java, I am following @AutomatedTester 's suggestions here, where I am keeping the implementation skeleton similar to what is done in .NET

Hope this helps!

Copy link
Member

@shs96c shs96c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good. Most of these are relatively small changes, I think.


public class Pdf {

private Object base64String;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be final and a String as well. Since the type is now known, we could call this base64EncodedPdf or similar.

this.base64String = base64String;
}

public String getBase64String() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getContent?

}

public String getBase64String() {
return (String) base64String;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cast should not be required.

package org.openqa.selenium;

public interface PrintsPage {
<X> X print(Object printOptions) throws WebDriverException;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pdf print(PrintOptions options)

Just returning X doesn't make it easy to use this API, since users don't know what X may be: once type erasure is applied, this ends up as Object. I'm not sure why you've created a PrintOptions class only to not use it here --- this seems like the ideal place, no?

@@ -159,6 +159,20 @@ public static int positive(String argName, Integer number, String message) {
return number;
}

public static double positive(String argName, Double number, String message) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're returning a double, you should make number a primitive type and not a wrapper type. This prevents an unnecessary boxing from occurring and obviates the need for the null check.

this.pageSize = pageSize;
}

public void setPageMargin(PageMargin margin) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check for null

return this.pageSize;
}

public PageMargin getPageMargin() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try and keep getters and setters next to each other. I like to have the getter immediately above the setter, but as long as you're consistent it'll be fine.

@@ -322,6 +325,14 @@ public String getCurrentUrl() {
}
}

@Override
public Pdf print(Object printOptions) throws WebDriverException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do a cast immediately below to PrintOptions, and so you've made a (fast, easy to fix) compile-time error into a (found later in the development cycle, harder to identify and therefore fix) runtime error.

Pdf print(PrintOptions) please.

public Pdf print(Object printOptions) throws WebDriverException {
Response response = execute(DriverCommand.PRINT_PAGE((PrintOptions) printOptions));

Object result = response.getValue();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest of the methods do a cast and assume the type is right. You can do return new Pdf((String) result);

import org.openqa.selenium.printoptions.PrintOptions;
import org.openqa.selenium.testing.JUnit4TestBase;

public class FireFoxPrintCommandTest extends JUnit4TestBase {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still needs to be lifted up to the main test suite, and this class removed. If chrome can't handle this when not headless we should file a bug with the chrome team, ignore the test for chrome, and link to that bug in the ignore's comment.

@diemol diemol mentioned this pull request Jan 14, 2021
@sonarqubecloud
Copy link

Copy link
Member

@shs96c shs96c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're nearly there :) Thank you for the revisions!


java_library(
name = "print",
srcs = glob(["**/*.java"]),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

*.java. Globbing to an indeterminate depth is seldom what we want to do.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file isn't needed at all

name = "print",
srcs = glob(["**/*.java"]),
visibility = [
"//visibility:public",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should only be visible to the java_export target that this is part of (and, if necessary, other libraries that are part of that target). Public visibility is almost always a mistake for something that isn't meant to ship as a standalone component.

public static double positive(String argName, double number, String message) {
if (number <= 0) {
if (message == null) {
throw new IllegalArgumentException(argName + " must be greater than 0");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer adding a second positive(String, double) that delegates down to this three-param version. Using null in code is generally Not A Great Idea, and it looks ugly.

"//visibility:public",
],
deps = [
"//java/client/src/org/openqa/selenium:core",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest that this package needs to be exported by core, so you may just want to add print/*.java to core's definition.

@Test
public void canPrintwoPages() {
PrintOptions printOptions = new PrintOptions();
printOptions.setPageRanges(new String[]{"1-2"});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is how we're expecting people to use this, then consider making setPageRanges take a mandatory first argument and String varargs to allow something like setPageRanges("1-2", "5-8")

@@ -44,7 +44,7 @@
public NodeStatus(
NodeId nodeId,
URI externalUri,
int maxSessionCount,
Integer maxSessionCount,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change shouldn't be necessary for this PR. Please remove it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bazel build fails otherwise. I have removed it from this PR.

Copy link
Member

@shs96c shs96c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of nits we can address in a follow up, but this looks fine.

@@ -25,6 +25,8 @@ java_export(
"mobile/*.java",
"net/*.java",
"virtualauthenticator/*.java",
"print/*.java",

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need this empty line.

return top;
}

public void setTop(double top) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussion in the selenium-tlc channel, we're leaning towards immutable data-types. We can remove the setters in a follow-up PR, since this review has already gone on a while and that's a new change

@shs96c shs96c merged commit 626f908 into SeleniumHQ:trunk Jan 19, 2021
@raju249 raju249 deleted the print-page-java branch January 19, 2021 10:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-java Java Bindings
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for print-to-PDF
4 participants