-
-
Notifications
You must be signed in to change notification settings - Fork 8.4k
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
Conversation
4e96560
to
e977ed1
Compare
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; |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Hey @luke-hill 👋 Regarding Java, I am following @AutomatedTester 's suggestions here, where I am keeping the implementation skeleton similar to what is done in Hope this helps! |
4cdd12a
to
e8dbb31
Compare
There was a problem hiding this 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; |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
8d35e5f
to
aa897c9
Compare
SonarCloud Quality Gate failed. |
There was a problem hiding this 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"]), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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"}); |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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", | |||
|
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
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
Checklist