├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── checkstyle-ignore.xml ├── checkstyle.xml ├── extension-clients ├── file-extension-client │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── sterodium │ │ │ └── extensions │ │ │ └── client │ │ │ ├── FileExtensionClient.java │ │ │ ├── delete │ │ │ └── FileDeleteRequest.java │ │ │ ├── download │ │ │ ├── FileDownloadException.java │ │ │ └── FileDownloadRequest.java │ │ │ └── upload │ │ │ ├── ResourceFolder.java │ │ │ ├── ResourcePackagingException.java │ │ │ ├── ResourceUploadException.java │ │ │ └── ResourceUploadRequest.java │ │ └── test │ │ ├── java │ │ └── io │ │ │ └── sterodium │ │ │ └── extensions │ │ │ └── client │ │ │ ├── BaseRequestTest.java │ │ │ ├── download │ │ │ └── FileDownloadRequestTest.java │ │ │ └── upload │ │ │ ├── ResourceFolderTest.java │ │ │ └── ResourceUploadRequestTest.java │ │ └── resources │ │ ├── files_for_upload │ │ ├── directory │ │ │ └── second.txt │ │ └── first.txt │ │ └── upload │ │ ├── directory │ │ ├── dir │ │ │ └── third.txt │ │ └── second.txt │ │ └── first.txt ├── pom.xml ├── sikuli-extension-client │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── sterodium │ │ │ └── extensions │ │ │ └── client │ │ │ └── SikuliExtensionClient.java │ │ └── test │ │ └── java │ │ └── io │ │ └── sterodium │ │ └── extensions │ │ └── client │ │ └── SikuliExtensionClientTest.java └── test-resources │ ├── pom.xml │ └── src │ └── main │ └── resources │ ├── flat │ ├── flat1.txt │ ├── flat2.txt │ └── flat3.txt │ └── hierarchy │ ├── level0.txt │ ├── level1.1 │ ├── level1.1.txt │ └── level2.2 │ │ └── level2.2.txt │ └── level1 │ ├── level1.txt │ └── level2 │ └── level2.txt ├── grid-starter ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── sterodium │ │ └── extensions │ │ ├── GridStarter.java │ │ └── spi │ │ └── GridConfigurator.java │ └── test │ └── java │ └── io │ └── sterodium │ └── extensions │ └── GridStarterTest.java ├── hub-extensions ├── extension-proxy │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── sterodium │ │ │ └── extensions │ │ │ ├── capability │ │ │ ├── Capabilities.java │ │ │ └── CustomCapabilityMatcher.java │ │ │ └── hub │ │ │ └── proxy │ │ │ ├── HubRequestsProxyingServlet.java │ │ │ ├── client │ │ │ ├── HttpClientProvider.java │ │ │ ├── HttpResponseConverter.java │ │ │ ├── RequestForwardingClient.java │ │ │ ├── RequestForwardingClientProvider.java │ │ │ └── UnsupportedHttpMethodException.java │ │ │ └── session │ │ │ └── SeleniumSessions.java │ │ └── test │ │ └── java │ │ └── io │ │ └── sterodium │ │ └── extensions │ │ ├── capability │ │ └── CustomCapabilityMatcherTest.java │ │ └── hub │ │ └── proxy │ │ ├── HubRequestsProxyingServletPathsTest.java │ │ ├── HubRequestsProxyingServletTest.java │ │ └── session │ │ └── SeleniumSessionsTest.java └── pom.xml ├── node-extensions ├── all-node-extensions │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── io │ │ └── sterodium │ │ └── extensions │ │ └── node │ │ └── DummyClass.java ├── file-extension │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── io │ │ │ └── sterodium │ │ │ └── extensions │ │ │ └── node │ │ │ ├── delete │ │ │ └── FileDeleteServlet.java │ │ │ ├── download │ │ │ └── FileDownloadServlet.java │ │ │ └── upload │ │ │ └── FileUploadServlet.java │ │ └── test │ │ └── java │ │ └── io │ │ └── sterodium │ │ └── extensions │ │ └── node │ │ ├── BaseServletTest.java │ │ ├── delete │ │ └── FileDeleteServletTest.java │ │ ├── download │ │ └── FileDownloadServletTest.java │ │ └── upload │ │ └── FileUploadServletTest.java ├── pom.xml └── sikuli-extension │ ├── pom.xml │ └── src │ ├── main │ └── java │ │ └── io │ │ └── sterodium │ │ └── extensions │ │ └── node │ │ ├── SikuliExtensionServlet.java │ │ └── rmi │ │ ├── SikuliApplication.java │ │ └── TargetFactory.java │ └── test │ ├── java │ └── io │ │ └── sterodium │ │ └── extensions │ │ └── node │ │ ├── SikuliExtensionServletTest.java │ │ └── rmi │ │ └── TargetFactoryTest.java │ └── resources │ └── level1 │ └── level2 │ └── level3 │ └── search_goal.txt ├── pom.xml ├── sample ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── sterodium │ │ │ └── sample │ │ │ └── LocalSeleniumGridLauncher.java │ └── resources │ │ ├── hubConfig.json │ │ └── nodeConfig.json │ └── test │ ├── java │ └── io │ │ └── sterodium │ │ └── sample │ │ └── test │ │ ├── AbstractTest.java │ │ ├── AceEditorTest.java │ │ ├── GoogleSearchTest.java │ │ └── ui │ │ ├── DefaultTextBox.java │ │ ├── DefaultUiComponent.java │ │ ├── ElementNotFoundException.java │ │ ├── SikuliHelper.java │ │ ├── TextBox.java │ │ └── UiComponent.java │ └── resources │ ├── grid.properties │ └── images │ ├── ace │ └── js_body.png │ └── google │ ├── search_button.png │ ├── search_field.png │ └── sikuli_logo.png ├── selenium-log4j ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── sterodium │ │ │ └── extensions │ │ │ └── log │ │ │ └── LoggingConfigurator.java │ └── resources │ │ └── META-INF │ │ └── services │ │ └── io.sterodium.extensions.spi.GridConfigurator │ └── test │ └── java │ └── io │ └── sterodium │ └── extensions │ └── log │ └── LoggingConfiguratorTest.java └── selenium-plugins └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | target 14 | 15 | # IntelliJ 16 | .idea/ 17 | *.iml 18 | *.iws 19 | *.prefs 20 | *.project 21 | *.classpath 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | - oraclejdk7 5 | 6 | after_success: 7 | - mvn cobertura:cobertura coveralls:report 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Selenium Grid Extensions 2 | 3 | [![Build Status](https://travis-ci.org/sterodium/selenium-grid-extensions.svg?branch=master)](https://travis-ci.org/sterodium/selenium-grid-extensions) 4 | [![Coverage Status](https://coveralls.io/repos/sterodium/selenium-grid-extensions/badge.svg?branch=master&service=github)](https://coveralls.io/github/sterodium/selenium-grid-extensions?branch=master) 5 | [![Maven Central](https://img.shields.io/maven-central/v/io.sterodium/selenium-grid-extensions.svg)](https://maven-badges.herokuapp.com/maven-central/io.sterodium/selenium-grid-extensions) 6 | [![][license img]][license] 7 | [![Gitter Chat](http://img.shields.io/badge/chat-online-brightgreen.svg)](https://gitter.im/sterodium/selenium-grid-extensions) 8 | 9 | 10 | Set of Selenium Grid extensions for a better UI tests. 11 | 12 | ## Full Documentation 13 | 14 | See the [Wiki](https://github.com/sterodium/selenium-grid-extensions/wiki) for documentation, examples and other information. 15 | 16 | ## Communication 17 | 18 | - Twitter: [@Sterodium](http://twitter.com/Sterodium) 19 | - [GitHub Issues](https://github.com/sterodium/selenium-grid-extensions/issues) 20 | - [Gitter Chat](https://gitter.im/sterodium/selenium-grid-extensions) 21 | 22 | ## What does this project do? 23 | 24 | #### 1) Allows you to run Sikuli tests remotely on the grid 25 | 26 | Remove pain of testing complex UI components by hacking DOM. With this extension you are able to combine Selenium tests 27 | with [Sikuli](http://www.sikuli.org/) image recognition and run them on the grid. 28 | 29 | Even more! Sikuli allows you to automate anything you see. 30 | 31 | #### 2) Download file from Selenium grid nodes 32 | 33 | Downloading files in Selenium tests? Get them to your machine and check contents. Now it's easy. 34 | 35 | #### 3) Upload resources on Selenium grid nodes 36 | 37 | You might have some file upload tests. Uploading files to remote environment is not a problem anymore. 38 | 39 | ## Getting started 40 | 41 | Extensions need to be installed on Selenium Grid. 42 | It is obligatory to have Extension proxy installed in the hub. 43 | 44 | 45 | 46 | ### Binaries 47 | 48 | Binaries and dependency information for Maven, Gradle and other build tools can be found at [http://search.maven.org](http://search.maven.org/#search%7Cga%7C1%7Cio.sterodium). 49 | 50 | ### Docker 51 | 52 | Look into project https://github.com/bravostudiodev/bravo-grid for docker images. 53 | 54 | #### On client side for Maven: 55 | 56 | ```xml 57 | 58 | io.sterodium 59 | sikuli-extension-client 60 | x.y.z 61 | 62 | 63 | io.sterodium 64 | file-extension-client 65 | x.y.z 66 | 67 | ``` 68 | 69 | #### On grid side: 70 | 71 | **Selenium hub:** 72 | 73 | - Get [Hub extensions proxy](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22io.sterodium%22%20AND%20a%3A%22extension-proxy%22) jar file and put it together with **selenium-server-standalone-x.y.z.jar** 74 | - Modify hubConfig.json (servlets and capability matcher property) 75 | ```json 76 | ... 77 | "servlet": "io.sterodium.extensions.hub.proxy.HubRequestsProxyingServlet", 78 | ... 79 | "capabilityMatcher": "io.sterodium.extensions.capability.CustomCapabilityMatcher" 80 | ... 81 | ``` 82 | Launch grid with 83 | ``` 84 | java -cp "selenium-server-standalone-3.0.1.jar:extension-proxy-x.y.z.jar" org.openqa.grid.selenium.GridLauncherV3 -role hub -hubConfig hubConfig.json 85 | ``` 86 | 87 | **Selenium node**: 88 | - Get [All node extensions](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22io.sterodium%22%20AND%20a%3A%22all-node-extensions%22) jar file and put it together with with **selenium-server-standalone-x.y.z.jar** 89 | - Modify **nodeConfig.json** (capabilities and servlet properties) 90 | 91 | ```json 92 | ... 93 | "capabilities": [ 94 | { 95 | "extension.sikuliCapability": true 96 | } 97 | ], 98 | "configuration": { 99 | ... 100 | "servlets": "io.sterodium.extensions.node.SikuliExtensionServlet,io.sterodium.extensions.node.upload.FileUploadServlet,io.sterodium.extensions.node.download.FileDownloadServlet" 101 | ... 102 | } 103 | ``` 104 | Launch node with 105 | ``` 106 | java -cp "selenium-server-standalone-3.0.1.jar:all-node-extensions-x.y.z.jar" org.openqa.grid.selenium.GridLauncherV3 -role node -nodeConfig nodeConfig.json 107 | ``` 108 | 109 | 110 | ### Hello world 111 | 112 | #### Sikuli extension 113 | 114 | Get session id from RemoteWebDriver 115 | 116 | ```java 117 | String sessionId = remoteWebDriver.getSessionId(); 118 | ``` 119 | 120 | Create Sikuli client and upload images to selenium node: 121 | 122 | ```java 123 | SikuliExtensionClient client = new SikuliExtensionClient(host, port, sessionId); 124 | client.uploadResourceBundle("my_images_folder"); 125 | ``` 126 | 127 | Locate image on screen and click: 128 | 129 | ```java 130 | TargetFactory targetFactory = client.getTargetFactory(); 131 | ImageTarget imageTarget = targetFactory.createImageTarget("image.png"); 132 | 133 | DesktopScreenRegion desktop = client.getDesktop(); 134 | ScreenRegion screenRegion = desktop.find(imageTarget); 135 | 136 | Mouse mouse = client.getMouse(); 137 | mouse.click(screenRegion.getCenter()); 138 | ``` 139 | 140 | Refer to [Sikuli docs](https://code.google.com/p/sikuli-api/wiki/BasicUsage?hl=en) for basic Sikuli API usage. 141 | 142 | #### File extension 143 | 144 | File upload to selenium node: 145 | 146 | ```java 147 | FileExtensionClient fileExtensionClient = new FileExtensionClient(host, port, sessionId); 148 | String uploadPath = fileExtensionClient.upload(resourceBundlePath); 149 | ``` 150 | 151 | File download from selenium node: 152 | 153 | ```java 154 | FileExtensionClient fileExtensionClient = new FileExtensionClient(host, port, sessionId); 155 | File fileFromNode = fileExtensionClient.download(pathToFile); 156 | ``` 157 | 158 | File deletion from selenium node: (which can be used as cleanup after upload/download activity performed on node) 159 | 160 | ```java 161 | FileExtensionClient fileExtensionClient = new FileExtensionClient(host, port, sessionId); 162 | boolean isFiledeleted = fileExtensionClient.delete(pathToFile); 163 | ``` 164 | 165 | ## Build 166 | 167 | ``` 168 | $ git clone git@github.com:sterodium/selenium-grid-extensions.git 169 | $ cd selenium-grid-extensions/ 170 | $ mvn install 171 | ``` 172 | 173 | [license]:LICENSE 174 | [license img]:https://img.shields.io/badge/License-Apache%202-blue.svg 175 | -------------------------------------------------------------------------------- /checkstyle-ignore.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /checkstyle.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | extension-clients 5 | io.sterodium 6 | 1.1-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | file-extension-client 11 | File Extension Client 12 | 13 | 14 | 15 | org.apache.httpcomponents 16 | httpclient 17 | 18 | 19 | org.zeroturnaround 20 | zt-zip 21 | 22 | 23 | org.slf4j 24 | slf4j-api 25 | 26 | 27 | com.google.guava 28 | guava 29 | 30 | 31 | 32 | 33 | org.seleniumhq.selenium 34 | selenium-server 35 | test 36 | 37 | 38 | 39 | io.sterodium 40 | file-extension 41 | ${project.version} 42 | test 43 | 44 | 45 | 46 | io.sterodium 47 | extension-proxy 48 | ${project.version} 49 | test 50 | 51 | 52 | 53 | io.sterodium 54 | test-resources 55 | ${project.version} 56 | test 57 | 58 | 59 | 60 | org.slf4j 61 | slf4j-simple 62 | test 63 | 64 | 65 | junit 66 | junit 67 | test 68 | 69 | 70 | org.mockito 71 | mockito-all 72 | test 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/main/java/io/sterodium/extensions/client/FileExtensionClient.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client; 2 | 3 | import io.sterodium.extensions.client.delete.FileDeleteRequest; 4 | import io.sterodium.extensions.client.download.FileDownloadRequest; 5 | import io.sterodium.extensions.client.upload.ResourceUploadRequest; 6 | 7 | import java.io.File; 8 | 9 | /** 10 | * @author Alexey Nikolaenko alexey@tcherezov.com 11 | * Date: 14/10/2015 12 | * Client for File Extension installed on Selenium Node 13 | */ 14 | public class FileExtensionClient { 15 | 16 | private FileDownloadRequest fileDownloadRequest; 17 | private ResourceUploadRequest resourceUploadRequest; 18 | private FileDeleteRequest fileDeleteRequest; 19 | 20 | /** 21 | * @param hubHost selenium hub host 22 | * @param hubPort selenium hub port 23 | * @param sessionId remote web driver session id 24 | */ 25 | public FileExtensionClient(String hubHost, int hubPort, String sessionId) { 26 | fileDownloadRequest = new FileDownloadRequest(hubHost, hubPort, sessionId); 27 | resourceUploadRequest = new ResourceUploadRequest(hubHost, hubPort, sessionId); 28 | fileDeleteRequest = new FileDeleteRequest(hubHost, hubPort, sessionId); 29 | 30 | } 31 | 32 | /** 33 | * Download file from Selenium Node. 34 | * 35 | * @param pathToFile absolute path to file 36 | * @return downloaded file in temp directory 37 | */ 38 | public File download(String pathToFile) { 39 | return fileDownloadRequest.download(pathToFile); 40 | } 41 | 42 | /** 43 | * Download file from Selenium Node. 44 | * 45 | * @param pathToFile absolute path to file 46 | * @return downloaded file with extension in temp directory 47 | */ 48 | public File download(String pathToFile, String extension) { 49 | return fileDownloadRequest.download(pathToFile, extension); 50 | } 51 | 52 | /** 53 | * Upload resource folder from classpath. 54 | * 55 | * @param resourcesPath path to resource folder in classpath 56 | * @return directory where files are stored on remote machine 57 | */ 58 | public String upload(String resourcesPath) { 59 | return resourceUploadRequest.upload(resourcesPath); 60 | } 61 | 62 | 63 | /** 64 | * 65 | * Delete file from Selenium Node 66 | * 67 | * @param pathToDelete absolute path of file which is to be deleted 68 | * @return boolean value true if file gets deleted and false if not 69 | */ 70 | public boolean delete(String pathToDelete) { 71 | return fileDeleteRequest.delete(pathToDelete); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/main/java/io/sterodium/extensions/client/delete/FileDeleteRequest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client.delete; 2 | 3 | import java.io.IOException; 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Base64; 6 | 7 | import org.apache.http.HttpHost; 8 | import org.apache.http.HttpStatus; 9 | import org.apache.http.client.methods.CloseableHttpResponse; 10 | import org.apache.http.client.methods.HttpGet; 11 | import org.apache.http.impl.client.CloseableHttpClient; 12 | import org.apache.http.impl.client.HttpClients; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | /** 17 | * 18 | * @author Sameer Jethvani 19 | * Date 22/06/2017 20 | * 21 | */ 22 | public class FileDeleteRequest { 23 | private static final Logger LOGGER = LoggerFactory 24 | .getLogger(FileDeleteRequest.class); 25 | 26 | public static final String FILE_DELETE_EXTENSION_PATH = "/grid/admin/HubRequestsProxyingServlet/session/%s/FileDeleteServlet/%s"; 27 | 28 | private final HttpHost httpHost; 29 | private final String sessionId; 30 | 31 | public FileDeleteRequest(String host, int port, String sessionId) { 32 | this.httpHost = new HttpHost(host, port); 33 | this.sessionId = sessionId; 34 | } 35 | 36 | public boolean delete(String pathToFile) { 37 | String encodedPath; 38 | encodedPath = Base64.getUrlEncoder().encodeToString(pathToFile.getBytes(StandardCharsets.UTF_8)); 39 | 40 | HttpGet request = new HttpGet(String.format(FILE_DELETE_EXTENSION_PATH, sessionId, encodedPath)); 41 | try { 42 | CloseableHttpClient httpClient = HttpClients.createDefault(); 43 | CloseableHttpResponse execute = httpClient.execute(httpHost, request); 44 | int statusCode = execute.getStatusLine().getStatusCode(); 45 | return HttpStatus.SC_OK == statusCode; 46 | } catch (IOException e) { 47 | return false; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/main/java/io/sterodium/extensions/client/download/FileDownloadException.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client.download; 2 | 3 | /** 4 | * @author Alexey Nikolaenko alexey@tcherezov.com 5 | * Date: 30/09/2015 6 | */ 7 | class FileDownloadException extends RuntimeException { 8 | 9 | public FileDownloadException(String message, int code) { 10 | super(String.format("Response returned code %d, with message: %s", code, message)); 11 | } 12 | 13 | public FileDownloadException(Throwable cause) { 14 | super(cause); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/main/java/io/sterodium/extensions/client/download/FileDownloadRequest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client.download; 2 | 3 | import org.apache.http.HttpHost; 4 | import org.apache.http.HttpStatus; 5 | import org.apache.http.client.methods.CloseableHttpResponse; 6 | import org.apache.http.client.methods.HttpGet; 7 | import org.apache.http.impl.client.CloseableHttpClient; 8 | import org.apache.http.impl.client.HttpClients; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.zeroturnaround.zip.commons.IOUtils; 12 | 13 | import java.io.File; 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.nio.charset.StandardCharsets; 18 | import java.util.Base64; 19 | 20 | /** 21 | * @author Alexey Nikolaenko alexey@tcherezov.com 22 | * Date: 30/09/2015 23 | */ 24 | public class FileDownloadRequest { 25 | private static final Logger LOGGER = LoggerFactory 26 | .getLogger(FileDownloadRequest.class); 27 | 28 | public static final String FILE_DOWNLOAD_EXTENSION_PATH = "/grid/admin/HubRequestsProxyingServlet/session/%s/FileDownloadServlet/%s"; 29 | 30 | private final HttpHost httpHost; 31 | private final String sessionId; 32 | 33 | public FileDownloadRequest(String host, int port, String sessionId) { 34 | this.httpHost = new HttpHost(host, port); 35 | this.sessionId = sessionId; 36 | } 37 | 38 | public File download(String pathToFile, String extension) { 39 | String encodedPath; 40 | encodedPath = Base64.getUrlEncoder().encodeToString(pathToFile.getBytes(StandardCharsets.UTF_8)); 41 | 42 | HttpGet request = new HttpGet(String.format(FILE_DOWNLOAD_EXTENSION_PATH, sessionId, encodedPath)); 43 | try { 44 | CloseableHttpClient httpClient = HttpClients.createDefault(); 45 | CloseableHttpResponse execute = httpClient.execute(httpHost, request); 46 | int statusCode = execute.getStatusLine().getStatusCode(); 47 | if (HttpStatus.SC_OK == statusCode) { 48 | File downloadResult = File.createTempFile("download_result", extension); 49 | try ( 50 | FileOutputStream outputStream = new FileOutputStream(downloadResult); 51 | InputStream responseStream = execute.getEntity().getContent()) { 52 | IOUtils.copy(responseStream, outputStream); 53 | } 54 | return downloadResult; 55 | } else { 56 | String message = IOUtils.toString(execute.getEntity().getContent(), "UTF-8"); 57 | throw new FileDownloadException(message, statusCode); 58 | } 59 | } catch (IOException e) { 60 | throw new FileDownloadException(e); 61 | } 62 | } 63 | 64 | public File download(String pathToFile) { 65 | String encodedPath; 66 | encodedPath = Base64.getUrlEncoder().encodeToString(pathToFile.getBytes(StandardCharsets.UTF_8)); 67 | 68 | HttpGet request = new HttpGet(String.format(FILE_DOWNLOAD_EXTENSION_PATH, sessionId, encodedPath)); 69 | try { 70 | CloseableHttpClient httpClient = HttpClients.createDefault(); 71 | CloseableHttpResponse execute = httpClient.execute(httpHost, request); 72 | int statusCode = execute.getStatusLine().getStatusCode(); 73 | if (HttpStatus.SC_OK == statusCode) { 74 | File downloadResult = File.createTempFile("download_result", ".tmp"); 75 | try ( 76 | FileOutputStream outputStream = new FileOutputStream(downloadResult); 77 | InputStream responseStream = execute.getEntity().getContent()) { 78 | IOUtils.copy(responseStream, outputStream); 79 | } 80 | return downloadResult; 81 | } else { 82 | String message = IOUtils.toString(execute.getEntity().getContent(), "UTF-8"); 83 | throw new FileDownloadException(message, statusCode); 84 | } 85 | } catch (IOException e) { 86 | throw new FileDownloadException(e); 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/main/java/io/sterodium/extensions/client/upload/ResourceFolder.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client.upload; 2 | 3 | import com.google.common.io.Resources; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.zeroturnaround.zip.NameMapper; 7 | import org.zeroturnaround.zip.ZipUtil; 8 | 9 | import java.io.File; 10 | import java.io.FileOutputStream; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.net.URISyntaxException; 14 | import java.net.URL; 15 | import java.net.URLDecoder; 16 | import java.util.Enumeration; 17 | import java.util.jar.JarEntry; 18 | import java.util.jar.JarFile; 19 | import java.util.zip.ZipEntry; 20 | import java.util.zip.ZipOutputStream; 21 | 22 | /** 23 | * @author Alexey Nikolaenko alexey@tcherezov.com 24 | * Date: 08/10/2015 25 | */ 26 | class ResourceFolder { 27 | 28 | public static final Logger LOGGER = LoggerFactory.getLogger(ResourceFolder.class); 29 | 30 | private String path; 31 | 32 | public ResourceFolder(String path) { 33 | this.path = path; 34 | } 35 | 36 | public File toZip() { 37 | LOGGER.debug("Zipping resource folder " + path); 38 | 39 | try { 40 | URL dirURL = Resources.getResource(path); 41 | if (dirURL == null) { 42 | throw new ResourcePackagingException("Failed to get resource at " + path); 43 | } 44 | 45 | File destZip = File.createTempFile("resources_", ".zip"); 46 | 47 | LOGGER.debug("Created temporary zip at " + destZip.getAbsolutePath()); 48 | LOGGER.debug("Resource protocol is " + dirURL.getProtocol()); 49 | 50 | if ("file".equals(dirURL.getProtocol())) { 51 | mapFolderFromResources(dirURL, destZip); 52 | } else { 53 | mapFolderFromJar(dirURL, destZip); 54 | } 55 | 56 | return destZip; 57 | } catch (Exception e) { 58 | throw new ResourcePackagingException(e); 59 | } 60 | } 61 | 62 | private void mapFolderFromResources(URL dirURL, File destZip) throws URISyntaxException { 63 | File srcFolder = new File(dirURL.toURI()); 64 | ZipUtil.pack(srcFolder, destZip, pathKeeper()); 65 | } 66 | 67 | private void mapFolderFromJar(URL dirURL, File destZip) throws IOException { 68 | try ( 69 | ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(destZip))) { 70 | 71 | String jarPath = substringJarPath(dirURL.getPath()); 72 | JarFile jar = new JarFile(URLDecoder.decode(jarPath, "UTF-8")); 73 | 74 | byte[] buffer = new byte[1024]; 75 | int len; 76 | 77 | Enumeration entries = jar.entries(); 78 | while (entries.hasMoreElements()) { 79 | JarEntry jarEntry = entries.nextElement(); 80 | String name = jarEntry.getName(); 81 | 82 | if (name.startsWith(path)) { 83 | LOGGER.debug("Zipping entry " + name); 84 | 85 | ZipEntry zipEntry = jar.getEntry(name); 86 | zip.putNextEntry(zipEntry); 87 | 88 | URL resource = Resources.getResource(zipEntry.getName()); 89 | try (InputStream inputStream = resource.openStream()) { 90 | while ((len = inputStream.read(buffer)) > 0) { 91 | zip.write(buffer, 0, len); 92 | } 93 | } 94 | zip.closeEntry(); 95 | } 96 | } 97 | } 98 | } 99 | 100 | /** 101 | * Example resource folder path may look like file:/C:/testware/target/ARTIFACT-1.0.0-SNAPSHOT.jar!/img 102 | *

103 | * Method cuts file: as first 5 symbols, 104 | * then cut any path after jar!.. 105 | * 106 | * @param resourceFolderPath 107 | * @return jar path 108 | */ 109 | private String substringJarPath(String resourceFolderPath) { 110 | return resourceFolderPath.substring(5, resourceFolderPath.indexOf("!")); 111 | } 112 | 113 | private NameMapper pathKeeper() { 114 | return new NameMapper() { 115 | @Override 116 | public String map(String name) { 117 | return path + "/" + name; 118 | } 119 | }; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/main/java/io/sterodium/extensions/client/upload/ResourcePackagingException.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client.upload; 2 | 3 | /** 4 | * @author Alexey Nikolaenko alexey@tcherezov.com 5 | * Date: 25/09/2015 6 | */ 7 | class ResourcePackagingException extends RuntimeException { 8 | 9 | public ResourcePackagingException(String message) { 10 | super(message); 11 | } 12 | 13 | public ResourcePackagingException(Throwable cause) { 14 | super(cause); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/main/java/io/sterodium/extensions/client/upload/ResourceUploadException.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client.upload; 2 | 3 | /** 4 | * @author Alexey Nikolaenko alexey@tcherezov.com 5 | * Date: 25/09/2015 6 | */ 7 | class ResourceUploadException extends RuntimeException { 8 | public ResourceUploadException(int code, String message) { 9 | super(String.format("Resource upload returned status %d with message: %s", code, message)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/main/java/io/sterodium/extensions/client/upload/ResourceUploadRequest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client.upload; 2 | 3 | import com.google.common.base.Throwables; 4 | 5 | import org.apache.http.Consts; 6 | import org.apache.http.HttpHost; 7 | import org.apache.http.HttpStatus; 8 | import org.apache.http.client.methods.CloseableHttpResponse; 9 | import org.apache.http.client.methods.HttpPost; 10 | import org.apache.http.entity.InputStreamEntity; 11 | import org.apache.http.impl.client.CloseableHttpClient; 12 | import org.apache.http.impl.client.HttpClients; 13 | import org.apache.http.protocol.HTTP; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | import org.zeroturnaround.zip.commons.IOUtils; 17 | 18 | import java.io.File; 19 | import java.io.FileInputStream; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | 23 | /** 24 | * @author Alexey Nikolaenko alexey@tcherezov.com 25 | * Date: 25/09/2015 26 | */ 27 | public class ResourceUploadRequest { 28 | 29 | private static final Logger LOGGER = LoggerFactory 30 | .getLogger(ResourceUploadRequest.class); 31 | 32 | public static final String FILE_UPLOAD_EXTENSION_PATH = "/grid/admin/HubRequestsProxyingServlet/session/%s/FileUploadServlet"; 33 | 34 | private final HttpHost httpHost; 35 | private final String sessionId; 36 | 37 | public ResourceUploadRequest(String host, int port, String sessionId) { 38 | this.httpHost = new HttpHost(host, port); 39 | this.sessionId = sessionId; 40 | } 41 | 42 | public String upload(String resourcesPath) { 43 | LOGGER.debug("Uploading resources from path:" + resourcesPath); 44 | 45 | File zip = addResourcesToZip(resourcesPath); 46 | 47 | CloseableHttpClient httpClient = HttpClients.createDefault(); 48 | 49 | HttpPost request = new HttpPost(String.format(FILE_UPLOAD_EXTENSION_PATH, sessionId)); 50 | request.setHeader(HTTP.CONTENT_TYPE, "application/octet-stream"); 51 | request.setHeader(HTTP.CONTENT_ENCODING, Consts.ISO_8859_1.name()); 52 | try { 53 | FileInputStream fileInputStream = new FileInputStream(zip); 54 | InputStreamEntity entity = new InputStreamEntity(fileInputStream); 55 | request.setEntity(entity); 56 | CloseableHttpResponse execute = httpClient.execute(httpHost, request); 57 | 58 | int statusCode = execute.getStatusLine().getStatusCode(); 59 | String content = contentAsString(execute); 60 | 61 | if (HttpStatus.SC_OK == statusCode) { 62 | return content; 63 | } else { 64 | throw new ResourceUploadException(statusCode, content); 65 | } 66 | } catch (IOException e) { 67 | throw Throwables.propagate(e); 68 | } 69 | } 70 | 71 | private String contentAsString(CloseableHttpResponse execute) throws IOException { 72 | InputStream response = execute.getEntity().getContent(); 73 | return IOUtils.toString(response, "utf-8"); 74 | } 75 | 76 | protected File addResourcesToZip(String resourcesPath) { 77 | ResourceFolder resourceFolder = new ResourceFolder(resourcesPath); 78 | return resourceFolder.toZip(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/test/java/io/sterodium/extensions/client/BaseRequestTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client; 2 | 3 | import org.seleniumhq.jetty9.server.Server; 4 | import org.seleniumhq.jetty9.servlet.ServletContextHandler; 5 | import org.seleniumhq.jetty9.servlet.ServletHolder; 6 | 7 | import javax.servlet.ServletException; 8 | import javax.servlet.http.HttpServlet; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | 13 | /** 14 | * @author Alexey Nikolaenko alexey@tcherezov.com 15 | * Date: 30/09/2015 16 | */ 17 | public abstract class BaseRequestTest { 18 | 19 | public static final String PATH = "/grid/admin/%s/session/%s/%s/%s"; 20 | 21 | protected Server startServerForServlet(HttpServlet servlet, String path) throws Exception { 22 | Server server = new Server(0); 23 | 24 | ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); 25 | context.setContextPath("/"); 26 | server.setHandler(context); 27 | 28 | context.addServlet(new ServletHolder(servlet), path); 29 | server.start(); 30 | 31 | return server; 32 | } 33 | 34 | public static class StubServlet extends HttpServlet { 35 | 36 | private Function function; 37 | 38 | public void setFunction(Function function) { 39 | this.function = function; 40 | } 41 | 42 | @Override 43 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 44 | function.apply(req, resp); 45 | } 46 | 47 | @Override 48 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 49 | function.apply(req, resp); 50 | } 51 | } 52 | 53 | protected interface Function { 54 | void apply(HttpServletRequest req, HttpServletResponse resp); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/test/java/io/sterodium/extensions/client/download/FileDownloadRequestTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client.download; 2 | 3 | import io.sterodium.extensions.client.BaseRequestTest; 4 | import io.sterodium.extensions.hub.proxy.HubRequestsProxyingServlet; 5 | import io.sterodium.extensions.node.download.FileDownloadServlet; 6 | import org.apache.commons.io.FileUtils; 7 | import org.apache.commons.io.IOUtils; 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.mockito.Mock; 13 | import org.mockito.invocation.InvocationOnMock; 14 | import org.mockito.runners.MockitoJUnitRunner; 15 | import org.mockito.stubbing.Answer; 16 | import org.seleniumhq.jetty9.server.AbstractNetworkConnector; 17 | import org.seleniumhq.jetty9.server.Server; 18 | 19 | import javax.servlet.ServletOutputStream; 20 | import javax.servlet.http.HttpServletRequest; 21 | import javax.servlet.http.HttpServletResponse; 22 | import java.io.File; 23 | import java.io.FileInputStream; 24 | import java.io.IOException; 25 | import java.nio.charset.StandardCharsets; 26 | import java.util.Base64; 27 | 28 | import static org.hamcrest.CoreMatchers.containsString; 29 | import static org.hamcrest.CoreMatchers.is; 30 | import static org.hamcrest.MatcherAssert.assertThat; 31 | import static org.junit.Assert.assertTrue; 32 | import static org.mockito.Mockito.any; 33 | import static org.mockito.Mockito.doAnswer; 34 | import static org.mockito.Mockito.times; 35 | import static org.mockito.Mockito.verify; 36 | 37 | /** 38 | * @author Alexey Nikolaenko alexey@tcherezov.com 39 | * Date: 30/09/2015 40 | */ 41 | @RunWith(MockitoJUnitRunner.class) 42 | public class FileDownloadRequestTest extends BaseRequestTest { 43 | 44 | private static final String SESSION_ID = "sessionId"; 45 | private static final String EXPECTED_CONTENT = "expected content"; 46 | 47 | @Mock 48 | Function responseHandleFunction; 49 | 50 | private File fileToGet; 51 | private Server server; 52 | private int port; 53 | private String extensionPath; 54 | 55 | @Before 56 | public void setUp() throws Exception { 57 | StubServlet stubServlet = new StubServlet(); 58 | stubServlet.setFunction(responseHandleFunction); 59 | 60 | fileToGet = File.createTempFile("test filename with spaces", ".txt"); 61 | FileUtils.write(fileToGet, EXPECTED_CONTENT, StandardCharsets.UTF_8); 62 | 63 | extensionPath = String.format(PATH, HubRequestsProxyingServlet.class.getSimpleName(), SESSION_ID, FileDownloadServlet.class.getSimpleName(), "*"); 64 | server = startServerForServlet(stubServlet, extensionPath); 65 | port = ((AbstractNetworkConnector) server.getConnectors()[0]).getLocalPort(); 66 | } 67 | 68 | @After 69 | public void tearDown() throws Exception { 70 | server.stop(); 71 | assertTrue(fileToGet.delete()); 72 | } 73 | 74 | @Test 75 | public void pathTemplateShouldHaveProperServletNames() { 76 | String resultPath = String.format(FileDownloadRequest.FILE_DOWNLOAD_EXTENSION_PATH, SESSION_ID, "*"); 77 | assertThat(resultPath, is(extensionPath)); 78 | } 79 | 80 | @Test 81 | public void testDownload() throws IOException { 82 | doAnswer(verifyRequestContent()) 83 | .when(responseHandleFunction) 84 | .apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 85 | 86 | FileDownloadRequest fileDownloadRequest = new FileDownloadRequest("localhost", port, SESSION_ID); 87 | File downloadedFile = fileDownloadRequest.download(fileToGet.getAbsolutePath()); 88 | 89 | try (FileInputStream fileInputStream = new FileInputStream(downloadedFile)) { 90 | String s = IOUtils.toString(fileInputStream, StandardCharsets.UTF_8); 91 | assertThat(s, is(EXPECTED_CONTENT)); 92 | } 93 | verify(responseHandleFunction, times(1)).apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 94 | } 95 | 96 | private Answer verifyRequestContent() { 97 | return new Answer() { 98 | @Override 99 | public Object answer(InvocationOnMock invocationOnMock) throws Throwable { 100 | HttpServletRequest req = (HttpServletRequest) invocationOnMock.getArguments()[0]; 101 | HttpServletResponse resp = (HttpServletResponse) invocationOnMock.getArguments()[1]; 102 | 103 | String pathInfo = req.getRequestURI().substring(req.getServletPath().length() + 1); 104 | pathInfo = new String(Base64.getUrlDecoder().decode(pathInfo.getBytes())); 105 | 106 | assertThat(pathInfo, containsString(fileToGet.getAbsolutePath())); 107 | 108 | try ( 109 | 110 | FileInputStream fileInputStream = new FileInputStream(new File(pathInfo)); 111 | ServletOutputStream outputStream = resp.getOutputStream()) { 112 | IOUtils.copy(fileInputStream, outputStream); 113 | } 114 | return null; 115 | } 116 | }; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/test/java/io/sterodium/extensions/client/upload/ResourceFolderTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client.upload; 2 | 3 | import com.google.common.io.Files; 4 | import org.junit.Test; 5 | import org.zeroturnaround.zip.ZipUtil; 6 | 7 | import java.io.File; 8 | 9 | import static org.junit.Assert.assertTrue; 10 | 11 | /** 12 | * @author Alexey Nikolaenko alexey@tcherezov.com 13 | * Date: 08/10/2015 14 | */ 15 | public class ResourceFolderTest { 16 | 17 | @Test 18 | public void shouldZipLocalResources() { 19 | ResourceFolder uploadFolder = new ResourceFolder("upload"); 20 | File file = uploadFolder.toZip(); 21 | 22 | File tempDir = Files.createTempDir(); 23 | ZipUtil.unpack(file, tempDir); 24 | 25 | verifyFilesInZip(tempDir, 26 | "upload", 27 | "upload/first.txt", 28 | "upload/directory", 29 | "upload/directory/second.txt", 30 | "upload/directory/dir", 31 | "upload/directory/dir/third.txt"); 32 | } 33 | 34 | @Test 35 | public void shouldZipExternalResources_FlatFolderCase() { 36 | ResourceFolder uploadFolder = new ResourceFolder("flat"); 37 | File file = uploadFolder.toZip(); 38 | 39 | File tempDir = Files.createTempDir(); 40 | ZipUtil.unpack(file, tempDir); 41 | 42 | verifyFilesInZip(tempDir, 43 | "flat", 44 | "flat/flat1.txt", 45 | "flat/flat2.txt", 46 | "flat/flat3.txt"); 47 | } 48 | 49 | @Test 50 | public void shouldZipExternalResources_HierarchicalFolderCase() { 51 | ResourceFolder uploadFolder = new ResourceFolder("hierarchy"); 52 | File file = uploadFolder.toZip(); 53 | 54 | File tempDir = Files.createTempDir(); 55 | ZipUtil.unpack(file, tempDir); 56 | 57 | verifyFilesInZip(tempDir, 58 | "hierarchy/level0.txt", 59 | "hierarchy/level1", 60 | "hierarchy/level1/level1.txt", 61 | "hierarchy/level1/level2", 62 | "hierarchy/level1/level2/level2.txt", 63 | "hierarchy/level1.1/level1.1.txt", 64 | "hierarchy/level1.1/level2.2", 65 | "hierarchy/level1.1/level2.2/level2.2.txt"); 66 | } 67 | 68 | private void verifyFilesInZip(File dir, String... paths) { 69 | for (String path : paths) { 70 | assertTrue(String.format("File %s not exists in dir: %s", path, dir.getAbsolutePath()), 71 | new File(dir, path).exists()); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/test/java/io/sterodium/extensions/client/upload/ResourceUploadRequestTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client.upload; 2 | 3 | import com.google.common.io.Files; 4 | import io.sterodium.extensions.client.BaseRequestTest; 5 | import io.sterodium.extensions.hub.proxy.HubRequestsProxyingServlet; 6 | import io.sterodium.extensions.node.upload.FileUploadServlet; 7 | import org.apache.commons.io.FileUtils; 8 | import org.apache.commons.io.IOUtils; 9 | import org.junit.After; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.mockito.Mock; 14 | import org.mockito.invocation.InvocationOnMock; 15 | import org.mockito.runners.MockitoJUnitRunner; 16 | import org.mockito.stubbing.Answer; 17 | import org.seleniumhq.jetty9.server.AbstractNetworkConnector; 18 | import org.seleniumhq.jetty9.server.Server; 19 | import org.zeroturnaround.zip.ZipUtil; 20 | 21 | import javax.servlet.ServletInputStream; 22 | import javax.servlet.http.HttpServletRequest; 23 | import javax.servlet.http.HttpServletResponse; 24 | import java.io.File; 25 | import java.io.FileOutputStream; 26 | import java.io.OutputStream; 27 | import java.nio.charset.Charset; 28 | 29 | import static org.hamcrest.CoreMatchers.containsString; 30 | import static org.hamcrest.CoreMatchers.is; 31 | import static org.hamcrest.CoreMatchers.startsWith; 32 | import static org.hamcrest.MatcherAssert.assertThat; 33 | import static org.junit.Assert.assertTrue; 34 | import static org.mockito.Mockito.any; 35 | import static org.mockito.Mockito.doAnswer; 36 | import static org.mockito.Mockito.times; 37 | import static org.mockito.Mockito.verify; 38 | 39 | /** 40 | * @author Alexey Nikolaenko alexey@tcherezov.com 41 | * Date: 28/09/2015 42 | * 43 | * Test verifies that ResoureUploadRequest makes proper upload request with zipped resource folder. 44 | * As result it should return contents of response as String. 45 | */ 46 | @RunWith(MockitoJUnitRunner.class) 47 | public class ResourceUploadRequestTest extends BaseRequestTest { 48 | 49 | 50 | private static final String EXPECTED_RETURN_FOLDER = "C:/Expected/Folder/"; 51 | private static final String SESSION_ID = "sessionId"; 52 | 53 | @Mock 54 | Function responseHandleFunction; 55 | 56 | private Server server; 57 | private int port; 58 | private String extensionPath; 59 | 60 | @Before 61 | public void setUp() throws Exception { 62 | StubServlet stubServlet = new StubServlet(); 63 | stubServlet.setFunction(responseHandleFunction); 64 | 65 | extensionPath = String.format(PATH, HubRequestsProxyingServlet.class.getSimpleName(), SESSION_ID, FileUploadServlet.class.getSimpleName(), "*"); 66 | server = startServerForServlet(stubServlet, extensionPath); 67 | port = ((AbstractNetworkConnector) server.getConnectors()[0]).getLocalPort(); 68 | } 69 | 70 | @After 71 | public void tearDown() throws Exception { 72 | server.stop(); 73 | } 74 | 75 | @Test 76 | public void pathTemplateShouldHaveProperServletNames() { 77 | String resultPath = String.format(ResourceUploadRequest.FILE_UPLOAD_EXTENSION_PATH, SESSION_ID); 78 | assertThat(extensionPath, startsWith(resultPath)); 79 | } 80 | 81 | @Test 82 | public void verifyUploadedContentHasProperStructureAndCanBeUnzipped() { 83 | doAnswer(verifyRequestContent()) 84 | .when(responseHandleFunction) 85 | .apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 86 | 87 | ResourceUploadRequest resourceUploadRequest = new ResourceUploadRequest("localhost", port, SESSION_ID); 88 | String responseWithPath = resourceUploadRequest.upload("files_for_upload"); 89 | 90 | verify(responseHandleFunction, times(1)).apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 91 | assertThat(responseWithPath, is(EXPECTED_RETURN_FOLDER)); 92 | } 93 | 94 | private Answer verifyRequestContent() { 95 | return new Answer() { 96 | @Override 97 | public Object answer(InvocationOnMock invocationOnMock) throws Throwable { 98 | HttpServletRequest req = (HttpServletRequest) invocationOnMock.getArguments()[0]; 99 | HttpServletResponse resp = (HttpServletResponse) invocationOnMock.getArguments()[1]; 100 | 101 | File tempFile = File.createTempFile("temp_resources", ".zip"); 102 | try (ServletInputStream inputStream = req.getInputStream(); 103 | OutputStream outputStream = new FileOutputStream(tempFile)) { 104 | IOUtils.copy(inputStream, outputStream); 105 | } 106 | File tempDir = Files.createTempDir(); 107 | ZipUtil.unpack(tempFile, tempDir); 108 | 109 | File firstTxtFile = new File(tempDir, "files_for_upload/first.txt"); 110 | File secondTxtFile = new File(tempDir, "files_for_upload/directory/second.txt"); 111 | 112 | assertTrue(firstTxtFile.exists()); 113 | assertTrue(secondTxtFile.exists()); 114 | 115 | //verify content 116 | String firstFileContent = Files.toString(firstTxtFile, Charset.defaultCharset()); 117 | assertThat(firstFileContent, containsString("content1")); 118 | String secondFileContent = Files.toString(secondTxtFile, Charset.defaultCharset()); 119 | assertThat(secondFileContent, containsString("content2")); 120 | 121 | //clean temp directories 122 | FileUtils.deleteDirectory(tempDir); 123 | assertTrue("Failed to delete uploaded zip", tempFile.delete()); 124 | 125 | 126 | //form response 127 | resp.getWriter().write(EXPECTED_RETURN_FOLDER); 128 | return null; 129 | } 130 | }; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/test/resources/files_for_upload/directory/second.txt: -------------------------------------------------------------------------------- 1 | content2 2 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/test/resources/files_for_upload/first.txt: -------------------------------------------------------------------------------- 1 | content1 2 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/test/resources/upload/directory/dir/third.txt: -------------------------------------------------------------------------------- 1 | third file 2 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/test/resources/upload/directory/second.txt: -------------------------------------------------------------------------------- 1 | second file 2 | -------------------------------------------------------------------------------- /extension-clients/file-extension-client/src/test/resources/upload/first.txt: -------------------------------------------------------------------------------- 1 | first file 2 | -------------------------------------------------------------------------------- /extension-clients/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | selenium-grid-extensions 5 | io.sterodium 6 | 1.1-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | extension-clients 11 | pom 12 | 13 | Extension clients 14 | 15 | 16 | file-extension-client 17 | test-resources 18 | sikuli-extension-client 19 | 20 | 21 | -------------------------------------------------------------------------------- /extension-clients/sikuli-extension-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | extension-clients 5 | io.sterodium 6 | 1.1-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | sikuli-extension-client 11 | Sikuli Extension Client 12 | 13 | 14 | 15 | 16 | io.sterodium 17 | sikuli-extension 18 | ${project.version} 19 | 20 | 21 | io.sterodium 22 | file-extension-client 23 | ${project.version} 24 | 25 | 26 | io.sterodium 27 | sterodium-rmi 28 | 29 | 30 | 31 | 32 | org.seleniumhq.selenium 33 | selenium-server 34 | test 35 | 36 | 37 | org.slf4j 38 | slf4j-simple 39 | test 40 | 41 | 42 | junit 43 | junit 44 | test 45 | 46 | 47 | io.sterodium 48 | extension-proxy 49 | ${project.version} 50 | test 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /extension-clients/sikuli-extension-client/src/main/java/io/sterodium/extensions/client/SikuliExtensionClient.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client; 2 | 3 | 4 | import io.sterodium.extensions.node.rmi.TargetFactory; 5 | import io.sterodium.rmi.protocol.client.RemoteNavigator; 6 | import org.sikuli.api.DesktopScreenRegion; 7 | import org.sikuli.api.robot.Keyboard; 8 | import org.sikuli.api.robot.Mouse; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.awt.datatransfer.Clipboard; 13 | 14 | /** 15 | * @author Mihails Volkovs mihails.volkovs@gmail.com 16 | * Date: 24/09/2015 17 | */ 18 | @SuppressWarnings("UnusedDeclaration") 19 | public final class SikuliExtensionClient { 20 | 21 | private static final Logger LOGGER = LoggerFactory 22 | .getLogger(SikuliExtensionClient.class); 23 | 24 | static final String SIKULI_EXTENSION_PATH = "/grid/admin/HubRequestsProxyingServlet/session/%s/SikuliExtensionServlet"; 25 | 26 | private RemoteNavigator navigator; 27 | private FileExtensionClient fileExtensionClient; 28 | 29 | public SikuliExtensionClient(String host, int port, String sessionId) { 30 | navigator = new RemoteNavigator(host, port, String.format(SIKULI_EXTENSION_PATH, sessionId)); 31 | fileExtensionClient = new FileExtensionClient(host, port, sessionId); 32 | } 33 | 34 | public Mouse getMouse() { 35 | return navigator.createProxy(Mouse.class, "mouse"); 36 | } 37 | 38 | public Keyboard getKeyboard() { 39 | return navigator.createProxy(Keyboard.class, "keyboard"); 40 | } 41 | 42 | public Clipboard getClipboard() { 43 | return navigator.createProxy(Clipboard.class, "clipboard"); 44 | } 45 | 46 | public DesktopScreenRegion getDesktop() { 47 | return navigator.createProxy(DesktopScreenRegion.class, "desktop"); 48 | } 49 | 50 | public TargetFactory getTargetFactory() { 51 | return navigator.createProxy(TargetFactory.class, "target-factory"); 52 | } 53 | 54 | public void uploadResourceBundle(String resourceBundlePath) { 55 | String upload = fileExtensionClient.upload(resourceBundlePath); 56 | LOGGER.debug("Resource bundle uploaded to " + upload); 57 | getTargetFactory().setImagePrefix(upload); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /extension-clients/sikuli-extension-client/src/test/java/io/sterodium/extensions/client/SikuliExtensionClientTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.client; 2 | 3 | import io.sterodium.extensions.hub.proxy.HubRequestsProxyingServlet; 4 | import io.sterodium.extensions.node.SikuliExtensionServlet; 5 | import org.junit.Test; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.hamcrest.MatcherAssert.assertThat; 9 | 10 | public class SikuliExtensionClientTest { 11 | 12 | public static final String PATH_TEMPLATE = "/grid/admin/%s/session/%s/%s"; 13 | 14 | @Test 15 | public void sikuliExtensionPathIsCorrect() { 16 | String expectedPath = String.format(PATH_TEMPLATE, 17 | HubRequestsProxyingServlet.class.getSimpleName(), 18 | "sessionId", 19 | SikuliExtensionServlet.class.getSimpleName()); 20 | 21 | String resultPath = String.format(SikuliExtensionClient.SIKULI_EXTENSION_PATH, "sessionId"); 22 | assertThat(resultPath, is(expectedPath)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /extension-clients/test-resources/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | extension-clients 5 | io.sterodium 6 | 1.1-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | test-resources 11 | 12 | Test Resources 13 | 14 | Module with different resource directory structures for Resource Upload tests. 15 | 16 | 17 | -------------------------------------------------------------------------------- /extension-clients/test-resources/src/main/resources/flat/flat1.txt: -------------------------------------------------------------------------------- 1 | flat1 2 | -------------------------------------------------------------------------------- /extension-clients/test-resources/src/main/resources/flat/flat2.txt: -------------------------------------------------------------------------------- 1 | flat2 2 | -------------------------------------------------------------------------------- /extension-clients/test-resources/src/main/resources/flat/flat3.txt: -------------------------------------------------------------------------------- 1 | flat3 2 | -------------------------------------------------------------------------------- /extension-clients/test-resources/src/main/resources/hierarchy/level0.txt: -------------------------------------------------------------------------------- 1 | level0 2 | -------------------------------------------------------------------------------- /extension-clients/test-resources/src/main/resources/hierarchy/level1.1/level1.1.txt: -------------------------------------------------------------------------------- 1 | level11 2 | -------------------------------------------------------------------------------- /extension-clients/test-resources/src/main/resources/hierarchy/level1.1/level2.2/level2.2.txt: -------------------------------------------------------------------------------- 1 | level22 2 | -------------------------------------------------------------------------------- /extension-clients/test-resources/src/main/resources/hierarchy/level1/level1.txt: -------------------------------------------------------------------------------- 1 | level1 2 | -------------------------------------------------------------------------------- /extension-clients/test-resources/src/main/resources/hierarchy/level1/level2/level2.txt: -------------------------------------------------------------------------------- 1 | level2 2 | -------------------------------------------------------------------------------- /grid-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | selenium-grid-extensions 5 | io.sterodium 6 | 1.1-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | grid-starter 11 | Grid Starter 12 | 13 | 14 | 15 | org.seleniumhq.selenium 16 | selenium-server 17 | provided 18 | 19 | 20 | org.slf4j 21 | jul-to-slf4j 22 | 1.7.12 23 | 24 | 25 | 26 | 27 | junit 28 | junit 29 | test 30 | 31 | 32 | 33 | 34 | 35 | 36 | org.apache.maven.plugins 37 | maven-shade-plugin 38 | 39 | 40 | package 41 | 42 | shade 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /grid-starter/src/main/java/io/sterodium/extensions/GridStarter.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions; 2 | 3 | import io.sterodium.extensions.spi.GridConfigurator; 4 | import org.openqa.grid.selenium.GridLauncherV3; 5 | import org.slf4j.bridge.SLF4JBridgeHandler; 6 | 7 | import java.util.ServiceLoader; 8 | 9 | /** 10 | * Installs JUL to SLF4J logging bridge and configures command line arguments before launching Selenium Grid server. 11 | * 12 | * @author Vladimir Ilyin ilyin371@gmail.com 13 | * Date: 20/11/2015 14 | */ 15 | public final class GridStarter { 16 | 17 | private String[] args; 18 | 19 | private GridStarter(String[] args) { 20 | this.args = args; 21 | } 22 | 23 | public static void main(String[] args) throws Exception { 24 | bridgeJulToSlf4j(); 25 | 26 | GridStarter starter = new GridStarter(args); 27 | starter.configure(); 28 | starter.start(); 29 | } 30 | 31 | private void configure() { 32 | ServiceLoader services = ServiceLoader.load(GridConfigurator.class); 33 | for (GridConfigurator gridConfigurator : services) { 34 | args = gridConfigurator.configure(args); 35 | } 36 | } 37 | 38 | static void bridgeJulToSlf4j() { 39 | if (!SLF4JBridgeHandler.isInstalled()) { 40 | SLF4JBridgeHandler.removeHandlersForRootLogger(); 41 | SLF4JBridgeHandler.install(); 42 | } 43 | } 44 | 45 | private void start() throws Exception { 46 | GridLauncherV3.main(args); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /grid-starter/src/main/java/io/sterodium/extensions/spi/GridConfigurator.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.spi; 2 | 3 | /** 4 | * @author Vladimir Ilyin ilyin371@gmail.com 5 | * Date: 20/11/2015 6 | */ 7 | public interface GridConfigurator { 8 | /** 9 | * Updates command line arguments 10 | * @param args 11 | * @return updated arguments 12 | */ 13 | String[] configure(String[] args); 14 | } 15 | -------------------------------------------------------------------------------- /grid-starter/src/test/java/io/sterodium/extensions/GridStarterTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions; 2 | 3 | import org.junit.Test; 4 | import org.slf4j.bridge.SLF4JBridgeHandler; 5 | 6 | import static org.junit.Assert.assertFalse; 7 | import static org.junit.Assert.assertTrue; 8 | 9 | public class GridStarterTest { 10 | 11 | @Test 12 | public void shouldInstallSLF4JBridge() throws Exception { 13 | assertFalse(SLF4JBridgeHandler.isInstalled()); 14 | GridStarter.bridgeJulToSlf4j(); 15 | assertTrue(SLF4JBridgeHandler.isInstalled()); 16 | } 17 | } -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | io.sterodium 5 | hub-extensions 6 | 1.1-SNAPSHOT 7 | ../pom.xml 8 | 9 | 4.0.0 10 | 11 | extension-proxy 12 | 13 | Selenium Hub Extension Proxy 14 | 15 | Hub Extension Proxy forwards requests to extensions installed on Selenium nodes. 16 | 17 | 18 | 19 | 20 | org.seleniumhq.selenium 21 | selenium-server 22 | provided 23 | 24 | 25 | 26 | junit 27 | junit 28 | test 29 | 30 | 31 | org.mockito 32 | mockito-all 33 | test 34 | 35 | 36 | org.hamcrest 37 | hamcrest-all 38 | test 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/src/main/java/io/sterodium/extensions/capability/Capabilities.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.capability; 2 | 3 | /** 4 | * @author Alexey Nikolaenko alexey@tcherezov.com 5 | * Date: 20/09/2015 6 | */ 7 | public final class Capabilities { 8 | 9 | public static final String EXTENSION_PREFIX = "extension."; 10 | 11 | private Capabilities() { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/src/main/java/io/sterodium/extensions/capability/CustomCapabilityMatcher.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.capability; 2 | 3 | import org.openqa.grid.internal.utils.DefaultCapabilityMatcher; 4 | 5 | import java.util.Map; 6 | import java.util.logging.Logger; 7 | 8 | /** 9 | * @author Alexey Nikolaenko alexey@tcherezov.com 10 | * Date: 20/09/2015 11 | * 12 | * Default capability matcher considers only 4 capabilities: 13 | * PLATFORM, BROWSER_NAME, VERSION, applicationName. 14 | * There are capabilities like FirefoxDriver.PROFILE which are used to pass profile settings to the node, these need to be ignored. 15 | * Custom capability matcher makes extension capability checks in addition to the basic ones. 16 | */ 17 | @SuppressWarnings("unused") 18 | public class CustomCapabilityMatcher extends DefaultCapabilityMatcher { 19 | 20 | private static final Logger LOGGER = Logger.getLogger(CustomCapabilityMatcher.class.getName()); 21 | 22 | @Override 23 | public boolean matches(Map nodeCapability, Map requestedCapability) { 24 | boolean basicChecks = super.matches(nodeCapability, requestedCapability); 25 | 26 | boolean customChecks = extensionCapabilityCheck(nodeCapability, requestedCapability); 27 | 28 | return basicChecks && customChecks; 29 | } 30 | 31 | private boolean extensionCapabilityCheck(Map nodeCapability, Map requestedCapability) { 32 | for (String capability : requestedCapability.keySet()) { 33 | if (capability.startsWith(Capabilities.EXTENSION_PREFIX)) { 34 | if (!requestedCapability.get(capability).equals(nodeCapability.get(capability))) { 35 | return false; 36 | } 37 | } 38 | } 39 | return true; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/src/main/java/io/sterodium/extensions/hub/proxy/HubRequestsProxyingServlet.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.hub.proxy; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import io.sterodium.extensions.hub.proxy.client.RequestForwardingClient; 5 | import io.sterodium.extensions.hub.proxy.client.RequestForwardingClientProvider; 6 | import io.sterodium.extensions.hub.proxy.session.SeleniumSessions; 7 | import org.openqa.grid.web.servlet.RegistryBasedServlet; 8 | import org.openqa.grid.internal.GridRegistry; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | import java.net.URL; 14 | import java.util.logging.Level; 15 | import java.util.logging.Logger; 16 | 17 | /** 18 | * @author Alexey Nikolaenko alexey@tcherezov.com 19 | * Date: 21/09/2015 20 | */ 21 | public class HubRequestsProxyingServlet extends RegistryBasedServlet { 22 | 23 | private static final Logger LOGGER = Logger.getLogger(HubRequestsProxyingServlet.class.getName()); 24 | 25 | @VisibleForTesting 26 | RequestForwardingClientProvider requestForwardingClientProvider; 27 | 28 | @SuppressWarnings("unused") 29 | public HubRequestsProxyingServlet() { 30 | this(null); 31 | } 32 | 33 | public HubRequestsProxyingServlet(GridRegistry registry) { 34 | super(registry); 35 | requestForwardingClientProvider = new RequestForwardingClientProvider(); 36 | } 37 | 38 | @Override 39 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 40 | forwardRequest(req, resp); 41 | } 42 | 43 | 44 | @Override 45 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 46 | forwardRequest(req, resp); 47 | } 48 | 49 | @Override 50 | protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 51 | forwardRequest(req, resp); 52 | } 53 | 54 | @Override 55 | protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 56 | forwardRequest(req, resp); 57 | } 58 | 59 | private void forwardRequest(HttpServletRequest req, HttpServletResponse resp) throws IOException { 60 | RequestForwardingClient requestForwardingClient; 61 | try { 62 | requestForwardingClient = createExtensionClient(req.getPathInfo()); 63 | } catch (IllegalArgumentException e) { 64 | resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); 65 | return; 66 | } 67 | 68 | try { 69 | requestForwardingClient.forwardRequest(req, resp); 70 | } catch (IOException e) { 71 | LOGGER.log(Level.SEVERE, "Exception during request forwarding", e); 72 | resp.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); 73 | } 74 | } 75 | 76 | private RequestForwardingClient createExtensionClient(String path) { 77 | LOGGER.info("Forwarding request with path: " + path); 78 | String sessionId = SeleniumSessions.getSessionIdFromPath(path); 79 | LOGGER.info("Retrieving remote host for session: " + sessionId); 80 | 81 | SeleniumSessions sessions = new SeleniumSessions(getRegistry()); 82 | sessions.refreshTimeout(sessionId); 83 | 84 | URL remoteHost = sessions.getRemoteHostForSession(sessionId); 85 | String host = remoteHost.getHost(); 86 | int port = remoteHost.getPort(); 87 | LOGGER.info("Remote host retrieved: " + host + ":" + port); 88 | 89 | return requestForwardingClientProvider.provide(host, port); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/src/main/java/io/sterodium/extensions/hub/proxy/client/HttpClientProvider.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.hub.proxy.client; 2 | 3 | import org.apache.http.impl.client.CloseableHttpClient; 4 | import org.apache.http.impl.client.HttpClients; 5 | 6 | /** 7 | * @author Alexey Nikolaenko alexey@tcherezov.com 8 | * Date: 21/09/2015 9 | */ 10 | public class HttpClientProvider { 11 | 12 | public CloseableHttpClient provide() { 13 | return HttpClients.createDefault(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/src/main/java/io/sterodium/extensions/hub/proxy/client/HttpResponseConverter.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.hub.proxy.client; 2 | 3 | import com.google.common.primitives.Ints; 4 | import org.apache.commons.io.IOUtils; 5 | import org.apache.http.Header; 6 | import org.apache.http.HttpEntity; 7 | import org.apache.http.HttpResponse; 8 | 9 | import javax.servlet.http.HttpServletResponse; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.OutputStream; 13 | import java.util.logging.Level; 14 | import java.util.logging.Logger; 15 | 16 | /** 17 | * @author Alexey Nikolaenko alexey@tcherezov.com 18 | * Date: 21/09/2015 19 | */ 20 | public final class HttpResponseConverter { 21 | 22 | private static final Logger LOGGER = Logger.getLogger(HttpResponseConverter.class.getName()); 23 | 24 | private HttpResponseConverter() { 25 | } 26 | 27 | public static void copy(HttpResponse source, HttpServletResponse target) { 28 | int statusCode = source.getStatusLine().getStatusCode(); 29 | target.setStatus(statusCode); 30 | LOGGER.info("Response from extension returned " + statusCode + " status code"); 31 | 32 | HttpEntity entity = source.getEntity(); 33 | 34 | Header contentType = entity.getContentType(); 35 | if (contentType != null) { 36 | target.setContentType(contentType.getValue()); 37 | LOGGER.info("Response from extension returned " + contentType.getValue() + " content type"); 38 | } 39 | 40 | long contentLength = entity.getContentLength(); 41 | target.setContentLength(Ints.checkedCast(contentLength)); 42 | LOGGER.info("Response from extension has " + contentLength + " content length"); 43 | 44 | LOGGER.info("Copying body content to original servlet response"); 45 | try (InputStream content = entity.getContent(); 46 | OutputStream response = target.getOutputStream()) { 47 | IOUtils.copy(content, response); 48 | } catch (IOException e) { 49 | LOGGER.log(Level.SEVERE, "Failed to copy response body content", e); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/src/main/java/io/sterodium/extensions/hub/proxy/client/RequestForwardingClient.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.hub.proxy.client; 2 | 3 | import io.sterodium.extensions.hub.proxy.session.SeleniumSessions; 4 | 5 | import org.apache.http.client.methods.CloseableHttpResponse; 6 | import org.apache.http.client.methods.HttpDelete; 7 | import org.apache.http.client.methods.HttpGet; 8 | import org.apache.http.client.methods.HttpPost; 9 | import org.apache.http.client.methods.HttpPut; 10 | import org.apache.http.client.methods.HttpRequestBase; 11 | import org.apache.http.entity.ContentType; 12 | import org.apache.http.entity.InputStreamEntity; 13 | import org.apache.http.impl.client.CloseableHttpClient; 14 | 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.servlet.http.HttpServletResponse; 17 | import java.io.IOException; 18 | import java.net.URI; 19 | import java.util.logging.Logger; 20 | 21 | /** 22 | * @author Alexey Nikolaenko alexey@tcherezov.com 23 | * Date: 22/09/2015 24 | */ 25 | public class RequestForwardingClient { 26 | 27 | private static final Logger LOGGER = Logger.getLogger(RequestForwardingClient.class.getName()); 28 | 29 | private static final String NODE_HOST = "http://%s:%d/extra"; 30 | 31 | private final HttpClientProvider httpClientProvider; 32 | private final String endpoint; 33 | 34 | public RequestForwardingClient(String host, int port) { 35 | this(String.format(NODE_HOST, host, port), new HttpClientProvider()); 36 | } 37 | 38 | public RequestForwardingClient(String endpoint, HttpClientProvider httpClientProvider) { 39 | this.httpClientProvider = httpClientProvider; 40 | this.endpoint = endpoint; 41 | } 42 | 43 | public void forwardRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException { 44 | try ( 45 | CloseableHttpClient httpClient = httpClientProvider.provide() 46 | ) { 47 | HttpRequestBase httpRequest = createHttpRequest(servletRequest); 48 | 49 | CloseableHttpResponse extensionResponse = httpClient.execute(httpRequest); 50 | HttpResponseConverter.copy(extensionResponse, servletResponse); 51 | } 52 | } 53 | 54 | private HttpRequestBase createHttpRequest(HttpServletRequest request) throws IOException { 55 | String method = request.getMethod(); 56 | LOGGER.info("Creating " + method + " request to forward"); 57 | HttpRequestBase httpRequestBase = HttpPost.METHOD_NAME.equals(method) ? createPostRequest(request) : 58 | HttpGet.METHOD_NAME.equals(method) ? new HttpGet() : 59 | HttpPut.METHOD_NAME.equals(method) ? new HttpPut() : 60 | HttpDelete.METHOD_NAME.equals(method) ? new HttpDelete() : null; 61 | 62 | if (httpRequestBase == null) { 63 | throw new UnsupportedHttpMethodException(method); 64 | } 65 | URI uri = URI.create(endpoint + SeleniumSessions.trimSessionPath(request.getPathInfo())); 66 | LOGGER.info("Trimming session id from path, new path: " + uri.toString()); 67 | httpRequestBase.setURI(uri); 68 | 69 | return httpRequestBase; 70 | } 71 | 72 | private HttpRequestBase createPostRequest(HttpServletRequest request) throws IOException { 73 | HttpPost httpPost = new HttpPost(); 74 | InputStreamEntity entity = new InputStreamEntity(request.getInputStream(), 75 | request.getContentLength(), 76 | // some requests contain ContentType;Encoding and fails in validation. 77 | // So striping Encoding and retaining only ContentType 78 | ContentType.create((request.getContentType().split(";")[0]))); 79 | httpPost.setEntity(entity); 80 | 81 | return httpPost; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/src/main/java/io/sterodium/extensions/hub/proxy/client/RequestForwardingClientProvider.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.hub.proxy.client; 2 | 3 | /** 4 | * @author Alexey Nikolaenko alexey@tcherezov.com 5 | * Date: 22/09/2015 6 | */ 7 | public class RequestForwardingClientProvider { 8 | public RequestForwardingClient provide(String host, int port) { 9 | return new RequestForwardingClient(host, port); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/src/main/java/io/sterodium/extensions/hub/proxy/client/UnsupportedHttpMethodException.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.hub.proxy.client; 2 | 3 | /** 4 | * @author Alexey Nikolaenko alexey@tcherezov.com 5 | * Date: 22/09/2015 6 | */ 7 | public class UnsupportedHttpMethodException extends RuntimeException { 8 | public UnsupportedHttpMethodException(String method) { 9 | super(String.format("Method %s is not supported", method)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/src/main/java/io/sterodium/extensions/hub/proxy/session/SeleniumSessions.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.hub.proxy.session; 2 | 3 | 4 | import org.openqa.grid.internal.TestSession; 5 | import org.openqa.grid.internal.GridRegistry; 6 | import java.net.URL; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * @author Alexey Nikolaenko alexey@tcherezov.com 12 | * Date: 21/09/2015 13 | */ 14 | public class SeleniumSessions { 15 | 16 | private static final Pattern SESSION_ID_PATTERN = Pattern.compile("/session/([^/]+).*"); 17 | 18 | private final GridRegistry registry; 19 | 20 | public SeleniumSessions(GridRegistry registry) { 21 | this.registry = registry; 22 | } 23 | 24 | public URL getRemoteHostForSession(String sessionId) { 25 | for (TestSession activeSession : registry.getActiveSessions()) { 26 | if (sessionId.equals(activeSession.getExternalKey().getKey())) { 27 | return activeSession.getSlot().getProxy().getRemoteHost(); 28 | } 29 | } 30 | throw new IllegalArgumentException("Invalid sessionId. No active session is present for id:" + sessionId); 31 | } 32 | 33 | public void refreshTimeout(String sessionId) { 34 | for (TestSession activeSession : registry.getActiveSessions()) { 35 | if ((activeSession != null) && (sessionId != null) && (activeSession.getExternalKey() != null)) { 36 | if (sessionId.equals(activeSession.getExternalKey().getKey())) { 37 | refreshTimeout(activeSession); 38 | } 39 | } 40 | } 41 | } 42 | 43 | private void refreshTimeout(TestSession activeSession) { 44 | if (activeSession.getInactivityTime() != 0) { 45 | activeSession.setIgnoreTimeout(false); 46 | } 47 | } 48 | 49 | public static String getSessionIdFromPath(String pathInfo) { 50 | Matcher matcher = SESSION_ID_PATTERN.matcher(pathInfo); 51 | if (matcher.matches()) { 52 | return matcher.group(1); 53 | } 54 | throw new IllegalArgumentException("Invalid request. Session Id is not present"); 55 | } 56 | 57 | public static String trimSessionPath(String pathInfo) { 58 | return pathInfo.replaceFirst("/session/" + getSessionIdFromPath(pathInfo), ""); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/src/test/java/io/sterodium/extensions/capability/CustomCapabilityMatcherTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.capability; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | import org.openqa.selenium.Platform; 6 | import org.openqa.selenium.remote.CapabilityType; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import static junit.framework.TestCase.assertFalse; 12 | import static junit.framework.TestCase.assertTrue; 13 | 14 | 15 | public class CustomCapabilityMatcherTest { 16 | 17 | private CustomCapabilityMatcher capabilityMatcher; 18 | 19 | @Before 20 | public void setUp() { 21 | capabilityMatcher = new CustomCapabilityMatcher(); 22 | } 23 | 24 | @Test 25 | public void shouldReturnTrueWithDefaultCapabilities() { 26 | Map nodeCapabilities = nodeCapabilities(); 27 | Map requestedCapabilities = requestedCapabilities(); 28 | assertTrue(capabilityMatcher.matches(nodeCapabilities, requestedCapabilities)); 29 | } 30 | 31 | @Test 32 | public void shouldReturnFalseWithDefaultCapabilitiesNotMatch() { 33 | Map nodeCapabilities = nodeCapabilities(); 34 | Map requestedCapabilities = requestedCapabilities(); 35 | requestedCapabilities.put(CapabilityType.VERSION, 10); 36 | 37 | assertFalse(capabilityMatcher.matches(nodeCapabilities, requestedCapabilities)); 38 | } 39 | 40 | @Test 41 | public void shouldReturnFalseWithDefaultCapabilitiesNotMatch_2() { 42 | Map nodeCapabilities = nodeCapabilities(); 43 | 44 | Map requestedCapabilities = requestedCapabilities(); 45 | requestedCapabilities.put(CapabilityType.PLATFORM, Platform.VISTA); 46 | 47 | assertFalse(capabilityMatcher.matches(nodeCapabilities, requestedCapabilities)); 48 | } 49 | 50 | @Test 51 | public void shouldReturnFalseWhenExtensionCapabilitiesAreNotFound() { 52 | Map nodeCapabilities = nodeCapabilities(); 53 | 54 | Map requestedCapabilities = requestedCapabilities(); 55 | requestedCapabilities.putAll(extensionCapabilities()); 56 | 57 | assertFalse(capabilityMatcher.matches(nodeCapabilities, requestedCapabilities)); 58 | } 59 | 60 | @Test 61 | public void shouldReturnFalseWhenExtensionCapabilitiesDoNotMatch() { 62 | Map nodeCapabilities = nodeCapabilities(); 63 | nodeCapabilities.put(Capabilities.EXTENSION_PREFIX + "sikuliCapability", false); 64 | 65 | Map requestedCapabilities = requestedCapabilities(); 66 | requestedCapabilities.put(Capabilities.EXTENSION_PREFIX + "sikuliCapability", true); 67 | 68 | assertFalse(capabilityMatcher.matches(nodeCapabilities, requestedCapabilities)); 69 | } 70 | 71 | @Test 72 | public void shouldReturnFalseWhenOneExtensionCapabilityDoNotMatch() { 73 | Map nodeCapabilities = nodeCapabilities(); 74 | nodeCapabilities.put(Capabilities.EXTENSION_PREFIX + "sikuliCapability", false); 75 | nodeCapabilities.put(Capabilities.EXTENSION_PREFIX + "ccc", SomeEnum.A); 76 | 77 | Map requestedCapabilities = requestedCapabilities(); 78 | requestedCapabilities.put(Capabilities.EXTENSION_PREFIX + "sikuliCapability", true); 79 | requestedCapabilities.put(Capabilities.EXTENSION_PREFIX + "ccc", SomeEnum.B); 80 | 81 | assertFalse(capabilityMatcher.matches(nodeCapabilities, requestedCapabilities)); 82 | } 83 | 84 | @Test 85 | public void shouldReturnTrueWhenNodeHasRequestedCapabilities() { 86 | Map nodeCapabilities = nodeCapabilities(); 87 | nodeCapabilities.putAll(extensionCapabilities()); 88 | 89 | Map requestedCapabilities = requestedCapabilities(); 90 | requestedCapabilities.put(Capabilities.EXTENSION_PREFIX + "sikuliCapability", true); 91 | 92 | assertTrue(capabilityMatcher.matches(nodeCapabilities, requestedCapabilities)); 93 | } 94 | 95 | @Test 96 | public void shouldReturnTrueWhenDefaultCapabilitiesMatchAndNodeHasExtensionCapabilities() { 97 | Map nodeCapabilities = nodeCapabilities(); 98 | nodeCapabilities.putAll(extensionCapabilities()); 99 | 100 | Map requestedCapabilities = requestedCapabilities(); 101 | 102 | assertTrue(capabilityMatcher.matches(nodeCapabilities, requestedCapabilities)); 103 | } 104 | 105 | private Map nodeCapabilities() { 106 | Map defaultCapabilities = new HashMap<>(); 107 | defaultCapabilities.put("seleniumProtocol", "WebDriver"); 108 | defaultCapabilities.put("maxInstances", 5); 109 | defaultCapabilities.put(CapabilityType.BROWSER_NAME, "firefox"); 110 | defaultCapabilities.put(CapabilityType.PLATFORM, Platform.WINDOWS); 111 | return defaultCapabilities; 112 | } 113 | 114 | private Map extensionCapabilities() { 115 | Map customCapabilities = new HashMap<>(); 116 | customCapabilities.put(Capabilities.EXTENSION_PREFIX + "sikuliCapability", true); 117 | customCapabilities.put(Capabilities.EXTENSION_PREFIX + "fileUploadCapability", false); 118 | customCapabilities.put(Capabilities.EXTENSION_PREFIX + "doNotFailWithExceptions", true); 119 | return customCapabilities; 120 | } 121 | 122 | private Map requestedCapabilities() { 123 | Map requestedCapabilities = new HashMap<>(); 124 | requestedCapabilities.put(CapabilityType.BROWSER_NAME, "firefox"); 125 | requestedCapabilities.put(CapabilityType.VERSION, null); 126 | requestedCapabilities.put(CapabilityType.PLATFORM, Platform.ANY); 127 | return requestedCapabilities; 128 | } 129 | 130 | private enum SomeEnum { 131 | A, 132 | B 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/src/test/java/io/sterodium/extensions/hub/proxy/HubRequestsProxyingServletPathsTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.hub.proxy; 2 | 3 | 4 | import io.sterodium.extensions.hub.proxy.client.HttpClientProvider; 5 | import io.sterodium.extensions.hub.proxy.client.RequestForwardingClient; 6 | import io.sterodium.extensions.hub.proxy.client.RequestForwardingClientProvider; 7 | import com.google.common.collect.Sets; 8 | import org.apache.commons.io.IOUtils; 9 | import org.apache.http.HttpEntity; 10 | import org.apache.http.client.methods.CloseableHttpResponse; 11 | import org.apache.http.client.methods.HttpUriRequest; 12 | import org.apache.http.impl.client.CloseableHttpClient; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import org.junit.runner.RunWith; 16 | import org.mockito.Answers; 17 | import org.mockito.Mock; 18 | import org.mockito.Mockito; 19 | import org.mockito.runners.MockitoJUnitRunner; 20 | import org.openqa.grid.internal.GridRegistry; 21 | import org.openqa.grid.internal.TestSession; 22 | 23 | import javax.servlet.*; 24 | import javax.servlet.http.HttpServletRequest; 25 | import javax.servlet.http.HttpServletResponse; 26 | import java.io.ByteArrayOutputStream; 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.net.URL; 30 | 31 | import static org.hamcrest.CoreMatchers.is; 32 | import static org.hamcrest.MatcherAssert.assertThat; 33 | import static org.mockito.Matchers.*; 34 | import static org.mockito.Mockito.verify; 35 | import static org.mockito.Mockito.when; 36 | 37 | /** 38 | * @author Alexey Nikolaenko alexey@tcherezov.com 39 | * Date: 21/09/2015 40 | */ 41 | @RunWith(MockitoJUnitRunner.class) 42 | public class HubRequestsProxyingServletPathsTest { 43 | 44 | @Mock 45 | HttpServletRequest req; 46 | @Mock 47 | HttpServletResponse resp; 48 | @Mock 49 | GridRegistry registry; 50 | 51 | @Mock(answer = Answers.RETURNS_DEEP_STUBS) 52 | TestSession activeSession1; 53 | @Mock(answer = Answers.RETURNS_DEEP_STUBS) 54 | TestSession activeSession2; 55 | 56 | @Mock(answer = Answers.RETURNS_DEEP_STUBS) 57 | HttpClientProvider httpClientProvider; 58 | @Mock 59 | RequestForwardingClientProvider requestForwardingClientProvider; 60 | @Mock(answer = Answers.RETURNS_DEEP_STUBS) 61 | CloseableHttpClient closeableHttpClient; 62 | @Mock(answer = Answers.RETURNS_DEEP_STUBS) 63 | CloseableHttpResponse closeableHttpResponse; 64 | @Mock(answer = Answers.RETURNS_DEEP_STUBS) 65 | HttpEntity entity; 66 | 67 | private HubRequestsProxyingServlet servlet; 68 | 69 | private final ByteArrayOutputStream httpServletResponseOutputStream = new ByteArrayOutputStream(4096); 70 | 71 | private final ServletOutputStream outputStream = new ServletOutputStream() { 72 | @Override 73 | public boolean isReady() { 74 | return true; 75 | } 76 | 77 | @Override 78 | public void setWriteListener(WriteListener writeListener) { 79 | throw new RuntimeException("Not implemented"); 80 | } 81 | 82 | @Override 83 | public void write(int b) throws IOException { 84 | httpServletResponseOutputStream.write(b); 85 | } 86 | }; 87 | 88 | private InputStream httpServletRequestInputStream; 89 | private final ServletInputStream inputStream = new ServletInputStream() { 90 | @Override 91 | public boolean isFinished() { 92 | try { 93 | return 0 == httpServletRequestInputStream.available(); 94 | } catch (IOException e) { 95 | e.printStackTrace(); 96 | } 97 | return true; 98 | } 99 | 100 | @Override 101 | public boolean isReady() { 102 | return true; 103 | } 104 | 105 | @Override 106 | public void setReadListener(ReadListener readListener) { 107 | throw new RuntimeException("Not implemented"); 108 | } 109 | 110 | @Override 111 | public int read() throws IOException { 112 | return httpServletRequestInputStream.read(); 113 | } 114 | }; 115 | 116 | @Before 117 | public void setUp() throws IOException { 118 | httpServletRequestInputStream = IOUtils.toInputStream("httpServletRequestInputStream", "UTF-8"); 119 | servlet = new HubRequestsProxyingServlet(registry); 120 | servlet.requestForwardingClientProvider = requestForwardingClientProvider; 121 | 122 | RequestForwardingClient requestForwardingClient = new RequestForwardingClient("test:5555", httpClientProvider); 123 | 124 | URL url = new URL("http://localhost:5555/"); 125 | 126 | when(registry.getActiveSessions()).thenReturn(Sets.newHashSet(activeSession1, activeSession2)); 127 | when(activeSession1.getExternalKey().getKey()).thenReturn("uuid1"); 128 | when(activeSession1.getSlot().getProxy().getRemoteHost()).thenReturn(url); 129 | when(activeSession2.getExternalKey().getKey()).thenReturn("uuid2"); 130 | 131 | when(req.getContentType()).thenReturn("application/json"); 132 | when(req.getInputStream()).thenReturn(inputStream); 133 | when(req.getContentLength()).thenReturn(29); 134 | 135 | when(requestForwardingClientProvider.provide(anyString(), anyInt())).thenReturn(requestForwardingClient); 136 | when(httpClientProvider.provide()).thenReturn(closeableHttpClient); 137 | 138 | when(closeableHttpClient.execute(Mockito.anyObject())).thenReturn(closeableHttpResponse); 139 | 140 | when(closeableHttpResponse.getStatusLine().getStatusCode()).thenReturn(200); 141 | when(closeableHttpResponse.getEntity()).thenReturn(entity); 142 | 143 | when(entity.getContent()).thenReturn(IOUtils.toInputStream("valid stream", "UTF-8")); 144 | when(entity.getContentLength()).thenReturn(12L); 145 | 146 | when(resp.getOutputStream()).thenReturn(outputStream); 147 | } 148 | 149 | @Test 150 | public void doGetWithValidSessionIdInPath() throws ServletException, IOException { 151 | when(req.getPathInfo()).thenReturn("/session/uuid1"); 152 | when(req.getMethod()).thenReturn("GET"); 153 | servlet.doGet(req, resp); 154 | 155 | assertThat(httpServletResponseOutputStream.toString(), is("valid stream")); 156 | } 157 | 158 | @Test 159 | public void doPostWithValidSessionIdInPath() throws ServletException, IOException { 160 | when(req.getPathInfo()).thenReturn("/session/uuid1"); 161 | when(req.getMethod()).thenReturn("POST"); 162 | servlet.doPost(req, resp); 163 | 164 | assertThat(httpServletResponseOutputStream.toString(), is("valid stream")); 165 | } 166 | 167 | @Test 168 | public void doGetWithInvalidSessionIdInPath() throws ServletException, IOException { 169 | when(req.getPathInfo()).thenReturn("/session"); 170 | servlet.doGet(req, resp); 171 | 172 | verify(resp).sendError(eq(HttpServletResponse.SC_BAD_REQUEST), anyString()); 173 | } 174 | 175 | @Test 176 | public void doGetWithInvalidSessionIdInPath_2() throws ServletException, IOException { 177 | when(req.getPathInfo()).thenReturn("/session/"); 178 | servlet.doGet(req, resp); 179 | 180 | verify(resp).sendError(eq(HttpServletResponse.SC_BAD_REQUEST), anyString()); 181 | } 182 | 183 | @Test 184 | public void doGetWithInvalidSessionIdInPath_3() throws ServletException, IOException { 185 | when(req.getPathInfo()).thenReturn("/8fba10d9-e2e4-498d-a192-555314658ab6/"); 186 | servlet.doGet(req, resp); 187 | 188 | verify(resp).sendError(eq(HttpServletResponse.SC_BAD_REQUEST), anyString()); 189 | } 190 | 191 | } 192 | -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/src/test/java/io/sterodium/extensions/hub/proxy/HubRequestsProxyingServletTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.hub.proxy; 2 | 3 | import com.google.common.collect.Sets; 4 | import com.google.common.net.MediaType; 5 | import org.apache.commons.io.IOUtils; 6 | import org.apache.http.HttpEntity; 7 | import org.apache.http.client.methods.CloseableHttpResponse; 8 | import org.apache.http.client.methods.HttpDelete; 9 | import org.apache.http.client.methods.HttpGet; 10 | import org.apache.http.client.methods.HttpPost; 11 | import org.apache.http.client.methods.HttpPut; 12 | import org.apache.http.client.utils.URIBuilder; 13 | import org.apache.http.entity.InputStreamEntity; 14 | import org.apache.http.impl.client.CloseableHttpClient; 15 | import org.apache.http.impl.client.HttpClients; 16 | import org.junit.After; 17 | import org.junit.Before; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.mockito.Answers; 21 | import org.mockito.Mock; 22 | import org.mockito.invocation.InvocationOnMock; 23 | import org.mockito.runners.MockitoJUnitRunner; 24 | import org.mockito.stubbing.Answer; 25 | import org.openqa.grid.internal.GridRegistry; 26 | import org.openqa.grid.internal.TestSession; 27 | import org.seleniumhq.jetty9.server.AbstractNetworkConnector; 28 | import org.seleniumhq.jetty9.server.Server; 29 | import org.seleniumhq.jetty9.servlet.ServletContextHandler; 30 | import org.seleniumhq.jetty9.servlet.ServletHolder; 31 | 32 | import javax.servlet.ServletException; 33 | import javax.servlet.http.HttpServlet; 34 | import javax.servlet.http.HttpServletRequest; 35 | import javax.servlet.http.HttpServletResponse; 36 | import java.io.ByteArrayInputStream; 37 | import java.io.IOException; 38 | import java.io.InputStream; 39 | import java.net.URL; 40 | import java.nio.charset.StandardCharsets; 41 | 42 | import static org.hamcrest.CoreMatchers.equalTo; 43 | import static org.hamcrest.CoreMatchers.is; 44 | import static org.junit.Assert.assertThat; 45 | import static org.mockito.Mockito.any; 46 | import static org.mockito.Mockito.doAnswer; 47 | import static org.mockito.Mockito.times; 48 | import static org.mockito.Mockito.verify; 49 | import static org.mockito.Mockito.when; 50 | 51 | /** 52 | * @author Alexey Nikolaenko alexey@tcherezov.com 53 | * Date: 24/09/2015 54 | */ 55 | @RunWith(MockitoJUnitRunner.class) 56 | public class HubRequestsProxyingServletTest { 57 | 58 | @Mock 59 | private Function mockedFunction; 60 | @Mock(answer = Answers.RETURNS_DEEP_STUBS) 61 | private GridRegistry mockedRegistry; 62 | @Mock(answer = Answers.RETURNS_DEEP_STUBS) 63 | private TestSession mockedSession; 64 | 65 | private int hubPort; 66 | private Server hubServer; 67 | private Server stubServer; 68 | 69 | @Before 70 | public void setUp() throws Exception { 71 | StubServlet stubServlet = new StubServlet(mockedFunction); 72 | HubRequestsProxyingServlet hubRequestsProxyingServlet = new HubRequestsProxyingServlet(mockedRegistry); 73 | 74 | hubServer = startServerForServlet(hubRequestsProxyingServlet, "/" + HubRequestsProxyingServlet.class.getSimpleName() + "/*"); 75 | hubPort = ((AbstractNetworkConnector) hubServer.getConnectors()[0]).getLocalPort(); 76 | 77 | stubServer = startServerForServlet(stubServlet, "/extra/stubbyExtension/*"); 78 | 79 | URL url = new URIBuilder("http://localhost:" + ((AbstractNetworkConnector) stubServer.getConnectors()[0]).getLocalPort()) 80 | .build() 81 | .toURL(); 82 | 83 | //Mock that registry contains session with url to redirect to 84 | when(mockedRegistry.getActiveSessions()).thenReturn(Sets.newHashSet(mockedSession)); 85 | when(mockedSession.getExternalKey().getKey()).thenReturn("session_id"); 86 | when(mockedSession.getSlot().getProxy().getRemoteHost()).thenReturn(url); 87 | } 88 | 89 | @After 90 | public void tearDown() throws Exception { 91 | hubServer.stop(); 92 | stubServer.stop(); 93 | } 94 | 95 | @Test 96 | public void shouldRedirectGetRequestAndTrimPathParams() throws IOException { 97 | doAnswer(verifyRequestPath()) 98 | .when(mockedFunction) 99 | .apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 100 | 101 | CloseableHttpClient httpClient = HttpClients.createDefault(); 102 | 103 | HttpGet httpGet = new HttpGet(String.format("http://localhost:%d/%s/session/%s/%s/proper/get/path/params", hubPort, 104 | HubRequestsProxyingServlet.class.getSimpleName(), "session_id", 105 | "stubbyExtension")); 106 | httpClient.execute(httpGet); 107 | 108 | verify(mockedFunction, times(1)).apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 109 | } 110 | 111 | @Test 112 | public void shouldRedirectPostRequestAndTrimPathParams() throws IOException { 113 | doAnswer(verifyRequestPath()) 114 | .when(mockedFunction) 115 | .apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 116 | 117 | CloseableHttpClient httpClient = HttpClients.createDefault(); 118 | 119 | HttpPost httpPost = new HttpPost(String.format("http://localhost:%d/%s/session/%s/%s/proper/get/path/params", hubPort, 120 | HubRequestsProxyingServlet.class.getSimpleName(), "session_id", 121 | "stubbyExtension")); 122 | setEntityWithExpectedContent(httpPost); 123 | httpClient.execute(httpPost); 124 | 125 | verify(mockedFunction, times(1)).apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 126 | } 127 | 128 | @Test 129 | public void shouldRedirectPutRequestAndTrimPathParams() throws IOException { 130 | doAnswer(verifyRequestPath()) 131 | .when(mockedFunction) 132 | .apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 133 | 134 | CloseableHttpClient httpClient = HttpClients.createDefault(); 135 | 136 | HttpPut httpPut = new HttpPut(String.format("http://localhost:%d/%s/session/%s/%s/proper/get/path/params", hubPort, 137 | HubRequestsProxyingServlet.class.getSimpleName(), "session_id", 138 | "stubbyExtension")); 139 | httpClient.execute(httpPut); 140 | 141 | verify(mockedFunction, times(1)).apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 142 | } 143 | 144 | @Test 145 | public void shouldRedirectDeleteRequestAndTrimPathParams() throws IOException { 146 | doAnswer(verifyRequestPath()) 147 | .when(mockedFunction) 148 | .apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 149 | 150 | CloseableHttpClient httpClient = HttpClients.createDefault(); 151 | 152 | HttpDelete httpPut = new HttpDelete(String.format("http://localhost:%d/%s/session/%s/%s/proper/get/path/params", hubPort, 153 | HubRequestsProxyingServlet.class.getSimpleName(), "session_id", 154 | "stubbyExtension")); 155 | httpClient.execute(httpPut); 156 | 157 | verify(mockedFunction, times(1)).apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 158 | } 159 | 160 | private Answer verifyRequestPath() { 161 | return new Answer() { 162 | @Override 163 | public Object answer(InvocationOnMock invocationOnMock) throws Throwable { 164 | HttpServletRequest req = (HttpServletRequest) invocationOnMock.getArguments()[0]; 165 | assertThat(req.getPathInfo(), is("/proper/get/path/params")); 166 | return null; 167 | } 168 | }; 169 | } 170 | 171 | @Test 172 | public void shouldRedirectPostRequestWithSameContents() throws IOException { 173 | doAnswer(verifyRequestContent(MediaType.OCTET_STREAM.toString())) 174 | .when(mockedFunction) 175 | .apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 176 | 177 | CloseableHttpClient httpClient = HttpClients.createDefault(); 178 | 179 | HttpPost httpPost = new HttpPost(String.format("http://localhost:%d/%s/session/%s/%s/proper/get/path/params", hubPort, 180 | HubRequestsProxyingServlet.class.getSimpleName(), "session_id", 181 | "stubbyExtension")); 182 | setEntityWithExpectedContent(httpPost); 183 | httpClient.execute(httpPost); 184 | 185 | verify(mockedFunction, times(1)).apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 186 | } 187 | 188 | private void setEntityWithExpectedContent(HttpPost httpPost) { 189 | InputStream inputStream = new ByteArrayInputStream("expected_content".getBytes()); 190 | InputStreamEntity entity = new InputStreamEntity(inputStream); 191 | entity.setContentType(MediaType.OCTET_STREAM.toString()); 192 | httpPost.setEntity(entity); 193 | } 194 | 195 | private Answer verifyRequestContent(final String contentType) { 196 | return new Answer() { 197 | @Override 198 | public Object answer(InvocationOnMock invocationOnMock) throws Throwable { 199 | HttpServletRequest req = (HttpServletRequest) invocationOnMock.getArguments()[0]; 200 | assertThat(req.getContentType(), equalTo(contentType)); 201 | String reqContent = IOUtils.toString(req.getInputStream(), StandardCharsets.UTF_8); 202 | assertThat(reqContent, is("expected_content")); 203 | return null; 204 | } 205 | }; 206 | } 207 | 208 | @Test 209 | public void shouldReturnPostResponseWithProperContents() throws IOException { 210 | doAnswer(constructResponse(MediaType.OCTET_STREAM.toString())) 211 | .when(mockedFunction) 212 | .apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 213 | 214 | CloseableHttpClient httpClient = HttpClients.createDefault(); 215 | 216 | HttpPost httpPost = new HttpPost(String.format("http://localhost:%d/%s/session/%s/%s/proper/get/path/params", hubPort, 217 | HubRequestsProxyingServlet.class.getSimpleName(), "session_id", 218 | "stubbyExtension")); 219 | setEntityWithExpectedContent(httpPost); 220 | CloseableHttpResponse httpResponse = httpClient.execute(httpPost); 221 | 222 | verify(mockedFunction, times(1)).apply(any(HttpServletRequest.class), any(HttpServletResponse.class)); 223 | 224 | HttpEntity httpEntity = httpResponse.getEntity(); 225 | assertThat(httpResponse.getStatusLine().getStatusCode(), is(HttpServletResponse.SC_CREATED)); 226 | assertThat(httpEntity.getContentType().getValue(), is(MediaType.OCTET_STREAM.toString())); 227 | 228 | String returnedContent = IOUtils.toString(httpEntity.getContent(), StandardCharsets.UTF_8); 229 | assertThat(returnedContent, is("expected_content")); 230 | } 231 | 232 | private Answer constructResponse(final String contentType) { 233 | return new Answer() { 234 | @Override 235 | public Object answer(InvocationOnMock invocationOnMock) throws Throwable { 236 | HttpServletResponse resp = (HttpServletResponse) invocationOnMock.getArguments()[1]; 237 | resp.setStatus(HttpServletResponse.SC_CREATED); 238 | resp.getOutputStream().write("expected_content".getBytes()); 239 | resp.setContentType(contentType); 240 | return null; 241 | } 242 | }; 243 | } 244 | 245 | private static class StubServlet extends HttpServlet { 246 | 247 | private final Function function; 248 | 249 | public StubServlet(Function function) { 250 | this.function = function; 251 | } 252 | 253 | @Override 254 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 255 | function.apply(req, resp); 256 | } 257 | 258 | @Override 259 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 260 | function.apply(req, resp); 261 | } 262 | 263 | @Override 264 | protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 265 | function.apply(req, resp); 266 | } 267 | 268 | @Override 269 | protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 270 | function.apply(req, resp); 271 | } 272 | } 273 | 274 | private interface Function { 275 | void apply(HttpServletRequest req, HttpServletResponse resp); 276 | } 277 | 278 | private Server startServerForServlet(HttpServlet servlet, String path) throws Exception { 279 | Server server = new Server(0); 280 | 281 | ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); 282 | context.setContextPath("/"); 283 | server.setHandler(context); 284 | 285 | context.addServlet(new ServletHolder(servlet), path); 286 | server.start(); 287 | 288 | return server; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /hub-extensions/extension-proxy/src/test/java/io/sterodium/extensions/hub/proxy/session/SeleniumSessionsTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.hub.proxy.session; 2 | 3 | import com.google.common.collect.Sets; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.mockito.Mock; 8 | import org.mockito.runners.MockitoJUnitRunner; 9 | import org.openqa.grid.internal.ExternalSessionKey; 10 | import org.openqa.grid.internal.GridRegistry; 11 | import org.openqa.grid.internal.TestSession; 12 | 13 | import static junit.framework.TestCase.assertTrue; 14 | import static org.junit.Assert.assertEquals; 15 | import static org.mockito.Mockito.spy; 16 | import static org.mockito.Mockito.when; 17 | 18 | import java.time.Clock; 19 | import java.time.Instant; 20 | 21 | /** 22 | * @author Alexey Nikolaenko alexey@tcherezov.com 23 | * Date: 22/09/2015 24 | */ 25 | @RunWith(MockitoJUnitRunner.class) 26 | public class SeleniumSessionsTest { 27 | 28 | @Mock 29 | GridRegistry registry; 30 | @Mock 31 | ExternalSessionKey externalSessionKey; 32 | 33 | TestSession activeSession; 34 | 35 | SeleniumSessions seleniumSessions; 36 | 37 | @Before 38 | public void setUp() { 39 | seleniumSessions = new SeleniumSessions(registry); 40 | activeSession = spy(new TestSession(null, null, Clock.systemDefaultZone())); 41 | 42 | when(activeSession.getExternalKey()).thenReturn(externalSessionKey); 43 | when(externalSessionKey.getKey()).thenReturn("sessionId"); 44 | when(registry.getActiveSessions()).thenReturn(Sets.newHashSet(activeSession)); 45 | } 46 | 47 | @Test(expected = IllegalArgumentException.class) 48 | public void getSessionIdExceptional() { 49 | SeleniumSessions.getSessionIdFromPath("/sessionId/"); 50 | } 51 | 52 | @Test 53 | public void getSessionIdFromPath() { 54 | assertEquals("sessionId", SeleniumSessions.getSessionIdFromPath("/session/sessionId/")); 55 | assertEquals("sessionId", SeleniumSessions.getSessionIdFromPath("/session/sessionId/getCurrentWindow")); 56 | } 57 | 58 | @Test(expected = IllegalArgumentException.class) 59 | public void trimSessionPathExceptional() { 60 | SeleniumSessions.trimSessionPath("/sessionId/"); 61 | } 62 | 63 | @Test 64 | public void trimSessionPath() { 65 | assertEquals("", SeleniumSessions.trimSessionPath("/session/sessionId")); 66 | assertEquals("/request", SeleniumSessions.trimSessionPath("/session/id/request")); 67 | assertEquals("/another/one", SeleniumSessions.trimSessionPath("/session/id/another/one")); 68 | } 69 | 70 | @Test 71 | public void shouldRefreshTimeout() throws InterruptedException { 72 | Thread.sleep(100); 73 | long inactivityTime = activeSession.getInactivityTime(); 74 | Thread.sleep(100); 75 | 76 | seleniumSessions.refreshTimeout("sessionId"); 77 | 78 | long inactivityTimeAfterRefresh = activeSession.getInactivityTime(); 79 | 80 | assertTrue(String.format("Inactivity time should be less after refresh, but have %d > %d", inactivityTime, inactivityTimeAfterRefresh), 81 | inactivityTime > inactivityTimeAfterRefresh); 82 | } 83 | 84 | @Test 85 | public void shouldNotRefreshTimeoutIfTimeoutIsIgnored() { 86 | activeSession.setIgnoreTimeout(true); 87 | 88 | long inactivityTime = activeSession.getInactivityTime(); 89 | 90 | seleniumSessions.refreshTimeout("sessionId"); 91 | 92 | long inactivityTimeAfterRefresh = activeSession.getInactivityTime(); 93 | 94 | assertTrue(inactivityTime == 0); 95 | assertTrue("Inactivity time should be 0 when timeout is ignored", inactivityTime == inactivityTimeAfterRefresh); 96 | } 97 | 98 | 99 | } 100 | -------------------------------------------------------------------------------- /hub-extensions/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | selenium-grid-extensions 5 | io.sterodium 6 | 1.1-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | hub-extensions 11 | pom 12 | Hub Extensions 13 | 14 | 15 | extension-proxy 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /node-extensions/all-node-extensions/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | node-extensions 5 | io.sterodium 6 | 1.1-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | all-node-extensions 11 | 12 | 13 | 14 | io.sterodium 15 | file-extension 16 | ${project.version} 17 | 18 | 19 | io.sterodium 20 | sikuli-extension 21 | ${project.version} 22 | 23 | 24 | 25 | 26 | 27 | 28 | org.apache.maven.plugins 29 | maven-shade-plugin 30 | 31 | 32 | package 33 | 34 | shade 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /node-extensions/all-node-extensions/src/main/java/io/sterodium/extensions/node/DummyClass.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.node; 2 | 3 | /** */ 4 | public class DummyClass { 5 | } 6 | -------------------------------------------------------------------------------- /node-extensions/file-extension/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | node-extensions 5 | io.sterodium 6 | 1.1-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | file-extension 11 | File Extension 12 | Extensions to support file operations over HTTP on Selenium Node 13 | 14 | 15 | 16 | org.seleniumhq.selenium 17 | selenium-server 18 | provided 19 | 20 | 21 | 22 | junit 23 | junit 24 | test 25 | 26 | 27 | org.mockito 28 | mockito-all 29 | test 30 | 31 | 32 | org.hamcrest 33 | hamcrest-all 34 | test 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /node-extensions/file-extension/src/main/java/io/sterodium/extensions/node/delete/FileDeleteServlet.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.node.delete; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.Base64; 6 | import java.util.logging.Logger; 7 | import javax.servlet.ServletException; 8 | import javax.servlet.http.HttpServlet; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | /** 13 | * @author Sameer Jethvani 14 | * Date: 22/06/2017 15 | *

16 | * Deletes file by providing it's path in GET request. 17 | */ 18 | public class FileDeleteServlet extends HttpServlet { 19 | 20 | private static final Logger LOGGER = Logger.getLogger(FileDeleteServlet.class.getName()); 21 | 22 | @Override 23 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 24 | String pathInfo = req.getRequestURI().substring(req.getServletPath().length() + 1); 25 | pathInfo = new String(Base64.getUrlDecoder().decode(pathInfo.getBytes())); 26 | LOGGER.info("Request for file delete received with path: " + pathInfo); 27 | 28 | File file = new File(pathInfo); 29 | if (!fileExistsAndNotDirectory(file, resp)) { 30 | return; 31 | } 32 | 33 | file.delete(); 34 | } 35 | 36 | private boolean fileExistsAndNotDirectory(File requestedFile, HttpServletResponse resp) throws IOException { 37 | if (!requestedFile.exists()) { 38 | LOGGER.info("Requested file doesn't exist: " + requestedFile.getAbsolutePath()); 39 | sendError(resp, "Requested file doesn't exist."); 40 | return false; 41 | } 42 | if (requestedFile.isDirectory()) { 43 | LOGGER.info("Requested file is directory: " + requestedFile.getAbsolutePath()); 44 | sendError(resp, "Requested file is directory."); 45 | return false; 46 | } 47 | if (!requestedFile.canRead()) { 48 | LOGGER.info("Requested file cannot bet read: " + requestedFile.getAbsolutePath()); 49 | sendError(resp, "Requested file can be read."); 50 | return false; 51 | } 52 | return true; 53 | } 54 | 55 | private void sendError(HttpServletResponse resp, String msg) throws IOException { 56 | resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); 57 | resp.getWriter().write(msg); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /node-extensions/file-extension/src/main/java/io/sterodium/extensions/node/download/FileDownloadServlet.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.node.download; 2 | 3 | import com.google.common.net.HttpHeaders; 4 | import com.google.common.net.MediaType; 5 | import org.apache.commons.io.IOUtils; 6 | 7 | import javax.servlet.ServletException; 8 | import javax.servlet.ServletOutputStream; 9 | import javax.servlet.http.HttpServlet; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.File; 13 | import java.io.FileInputStream; 14 | import java.io.IOException; 15 | import java.nio.file.Files; 16 | import java.util.Base64; 17 | import java.util.logging.Logger; 18 | 19 | /** 20 | * @author Alexey Nikolaenko alexey@tcherezov.com 21 | * Date: 22/09/2015 22 | * 23 | * Allows to download file by providing it's path in GET request. 24 | */ 25 | public class FileDownloadServlet extends HttpServlet { 26 | 27 | private static final Logger LOGGER = Logger.getLogger(FileDownloadServlet.class.getName()); 28 | 29 | @Override 30 | protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 31 | String pathInfo = req.getRequestURI().substring(req.getServletPath().length() + 1); 32 | pathInfo = new String(Base64.getUrlDecoder().decode(pathInfo.getBytes())); 33 | LOGGER.info("Request for file download received with path: " + pathInfo); 34 | 35 | File file = new File(pathInfo); 36 | if (!fileExistsAndNotDirectory(file, resp)) { 37 | return; 38 | } 39 | 40 | String contentType = identifyFileContentType(file); 41 | resp.setContentType(contentType); 42 | resp.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getName()); 43 | 44 | try ( 45 | FileInputStream fileInputStream = new FileInputStream(file); 46 | ServletOutputStream outputStream = resp.getOutputStream()) { 47 | IOUtils.copy(fileInputStream, outputStream); 48 | } 49 | } 50 | 51 | private String identifyFileContentType(File file) throws IOException { 52 | String contentType = Files.probeContentType(file.toPath()); 53 | return contentType != null ? contentType : MediaType.OCTET_STREAM.toString(); 54 | } 55 | 56 | private boolean fileExistsAndNotDirectory(File requestedFile, HttpServletResponse resp) throws IOException { 57 | if (!requestedFile.exists()) { 58 | LOGGER.info("Requested file doesn't exist: " + requestedFile.getAbsolutePath()); 59 | sendError(resp, "Requested file doesn't exist."); 60 | return false; 61 | } 62 | if (requestedFile.isDirectory()) { 63 | LOGGER.info("Requested file is directory: " + requestedFile.getAbsolutePath()); 64 | sendError(resp, "Requested file is directory."); 65 | return false; 66 | } 67 | if (!requestedFile.canRead()) { 68 | LOGGER.info("Requested file cannot bet read: " + requestedFile.getAbsolutePath()); 69 | sendError(resp, "Requested file can be read."); 70 | return false; 71 | } 72 | return true; 73 | } 74 | 75 | private void sendError(HttpServletResponse resp, String msg) throws IOException { 76 | resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); 77 | resp.getWriter().write(msg); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /node-extensions/file-extension/src/main/java/io/sterodium/extensions/node/upload/FileUploadServlet.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.node.upload; 2 | 3 | import com.google.common.io.Files; 4 | import com.google.common.net.MediaType; 5 | import org.apache.commons.io.IOUtils; 6 | 7 | import javax.servlet.ServletException; 8 | import javax.servlet.ServletInputStream; 9 | import javax.servlet.http.HttpServlet; 10 | import javax.servlet.http.HttpServletRequest; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.File; 13 | import java.io.FileOutputStream; 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.io.OutputStream; 17 | import java.io.PrintWriter; 18 | import java.util.Enumeration; 19 | import java.util.logging.Logger; 20 | import java.util.zip.ZipEntry; 21 | import java.util.zip.ZipFile; 22 | 23 | /** 24 | * @author Alexey Nikolaenko alexey@tcherezov.com 25 | * Date: 22/09/2015 26 | * 27 | * Allows to upload zip archive with resources. 28 | * Zip contents will be stored in temporary folder, 29 | * absolute path will be returned in response body. 30 | */ 31 | public class FileUploadServlet extends HttpServlet { 32 | 33 | private static final Logger LOGGER = Logger.getLogger(FileUploadServlet.class.getName()); 34 | 35 | @Override 36 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 37 | LOGGER.info(String.format("Request Content-Type: %s, Content-Length:%d", req.getContentType(), req.getContentLength())); 38 | 39 | if (!MediaType.OCTET_STREAM.toString().equals(req.getContentType())) { 40 | resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Content type " + req.getContentType() + " is not supported"); 41 | return; 42 | } 43 | 44 | LOGGER.info("Creating temporary file"); 45 | File tempFile = File.createTempFile("selenium_node", ".zip"); 46 | try (ServletInputStream inputStream = req.getInputStream(); 47 | OutputStream outputStream = new FileOutputStream(tempFile)) { 48 | LOGGER.info("Copying request input stream to file"); 49 | IOUtils.copy(inputStream, outputStream); 50 | } 51 | 52 | LOGGER.info("Unzipping zip archive"); 53 | File imagesBaseDir = unZip(tempFile); 54 | if (!tempFile.delete()) { 55 | throw new IOException("Unable to delete file: " + tempFile); 56 | } 57 | 58 | LOGGER.info("Writing directory to response"); 59 | PrintWriter writer = resp.getWriter(); 60 | writer.write(imagesBaseDir.getAbsolutePath()); 61 | } 62 | 63 | private static File unZip(final File zippedFile) throws IOException { 64 | File outputFolder = Files.createTempDir(); 65 | try (ZipFile zipFile = new ZipFile(zippedFile)) { 66 | final Enumeration entries = zipFile.entries(); 67 | while (entries.hasMoreElements()) { 68 | final ZipEntry entry = entries.nextElement(); 69 | final File entryDestination = new File(outputFolder, entry.getName()); 70 | if (entry.isDirectory()) { 71 | //noinspection ResultOfMethodCallIgnored 72 | entryDestination.mkdirs(); 73 | } else { 74 | //noinspection ResultOfMethodCallIgnored 75 | entryDestination.getParentFile().mkdirs(); 76 | final InputStream in = zipFile.getInputStream(entry); 77 | final OutputStream out = new FileOutputStream(entryDestination); 78 | IOUtils.copy(in, out); 79 | IOUtils.closeQuietly(in); 80 | out.close(); 81 | } 82 | } 83 | } 84 | return outputFolder; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /node-extensions/file-extension/src/test/java/io/sterodium/extensions/node/BaseServletTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.node; 2 | 3 | import org.apache.commons.io.IOUtils; 4 | import org.seleniumhq.jetty9.server.Server; 5 | import org.seleniumhq.jetty9.servlet.ServletContextHandler; 6 | import org.seleniumhq.jetty9.servlet.ServletHolder; 7 | 8 | import javax.servlet.http.HttpServlet; 9 | import java.io.File; 10 | import java.io.FileOutputStream; 11 | import java.io.IOException; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.zip.ZipEntry; 14 | import java.util.zip.ZipOutputStream; 15 | 16 | import static org.hamcrest.CoreMatchers.is; 17 | import static org.junit.Assert.assertThat; 18 | 19 | /** 20 | * @author Alexey Nikolaenko alexey@tcherezov.com 21 | * Date: 24/09/2015 22 | */ 23 | public abstract class BaseServletTest { 24 | 25 | public static final String ZIP_FILE_NAME = "test_entry.txt"; 26 | 27 | protected Server startServerForServlet(HttpServlet servlet, String path) throws Exception { 28 | Server server = new Server(0); 29 | 30 | ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); 31 | context.setContextPath("/"); 32 | server.setHandler(context); 33 | 34 | context.addServlet(new ServletHolder(servlet), path); 35 | server.start(); 36 | 37 | return server; 38 | } 39 | 40 | protected File createZipArchiveWithTextFile() throws IOException { 41 | final File zipArchive = File.createTempFile("temp_zip_", ".zip"); 42 | try ( 43 | final ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipArchive))) { 44 | ZipEntry e = new ZipEntry(ZIP_FILE_NAME); 45 | out.putNextEntry(e); 46 | IOUtils.write("test data", out, StandardCharsets.UTF_8); 47 | out.closeEntry(); 48 | } 49 | return zipArchive; 50 | } 51 | 52 | protected void deleteIfExists(File... files) { 53 | for (File file : files) { 54 | if (file != null && file.exists()) { 55 | assertThat("Couldn't clean file after tests", file.delete(), is(true)); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /node-extensions/file-extension/src/test/java/io/sterodium/extensions/node/delete/FileDeleteServletTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.node.delete; 2 | 3 | import io.sterodium.extensions.node.BaseServletTest; 4 | import org.apache.commons.io.FileUtils; 5 | import org.apache.http.HttpHost; 6 | import org.apache.http.client.methods.CloseableHttpResponse; 7 | import org.apache.http.client.methods.HttpGet; 8 | import org.apache.http.impl.client.CloseableHttpClient; 9 | import org.apache.http.impl.client.HttpClients; 10 | import org.junit.After; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.seleniumhq.jetty9.server.AbstractNetworkConnector; 14 | import org.seleniumhq.jetty9.server.Server; 15 | 16 | import java.io.File; 17 | import java.io.IOException; 18 | import java.net.URLEncoder; 19 | import java.nio.charset.StandardCharsets; 20 | import java.util.Base64; 21 | 22 | import static org.hamcrest.CoreMatchers.is; 23 | import static org.hamcrest.MatcherAssert.assertThat; 24 | 25 | /** 26 | * @author Sameer Jethvani (s.jethvani@gmail.com) 27 | * Date: 22/06/2017 28 | */ 29 | public class FileDeleteServletTest extends BaseServletTest { 30 | 31 | private Server filedeleteServer; 32 | private HttpHost serverHost; 33 | 34 | @Before 35 | public void setUp() throws Exception { 36 | filedeleteServer = startServerForServlet(new FileDeleteServlet(), "/" + FileDeleteServlet.class.getSimpleName() + "/*"); 37 | serverHost = new HttpHost("localhost", ((AbstractNetworkConnector) filedeleteServer.getConnectors()[0]).getLocalPort()); 38 | } 39 | 40 | @After 41 | public void tearDown() throws Exception { 42 | filedeleteServer.stop(); 43 | } 44 | 45 | @Test 46 | public void getShouldDeleteFile() throws IOException { 47 | File fileTobeDeleted = File.createTempFile("testDeleteFile", ".txt"); 48 | FileUtils.write(fileTobeDeleted, "file_to_be_deleted_content", StandardCharsets.UTF_8); 49 | 50 | CloseableHttpClient httpClient = HttpClients.createDefault(); 51 | 52 | String encode = Base64.getUrlEncoder().encodeToString(fileTobeDeleted.getAbsolutePath().getBytes(StandardCharsets.UTF_8)); 53 | HttpGet httpGet = new HttpGet("/FileDeleteServlet/" + encode); 54 | 55 | CloseableHttpResponse execute = httpClient.execute(serverHost, httpGet); 56 | 57 | //check file got deleted 58 | //Assert.assertFalse(fileTobeDeleted.getAbsolutePath()+" File should have been deleted ",fileTobeDeleted.exists()); 59 | assertThat(fileTobeDeleted+" File should have been deleted . It should not exist at this point",fileTobeDeleted.exists(), is(false)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /node-extensions/file-extension/src/test/java/io/sterodium/extensions/node/download/FileDownloadServletTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.node.download; 2 | 3 | import io.sterodium.extensions.node.BaseServletTest; 4 | import com.google.common.io.Files; 5 | import com.google.common.net.HttpHeaders; 6 | import org.apache.commons.io.FileUtils; 7 | import org.apache.commons.io.IOUtils; 8 | import org.apache.http.Header; 9 | import org.apache.http.HttpHost; 10 | import org.apache.http.HttpStatus; 11 | import org.apache.http.client.methods.CloseableHttpResponse; 12 | import org.apache.http.client.methods.HttpGet; 13 | import org.apache.http.impl.client.CloseableHttpClient; 14 | import org.apache.http.impl.client.HttpClients; 15 | import org.junit.After; 16 | import org.junit.Before; 17 | import org.junit.Test; 18 | import org.seleniumhq.jetty9.server.AbstractNetworkConnector; 19 | import org.seleniumhq.jetty9.server.Server; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.nio.charset.StandardCharsets; 25 | import java.util.Base64; 26 | 27 | import static junit.framework.TestCase.assertTrue; 28 | import static org.hamcrest.CoreMatchers.containsString; 29 | import static org.hamcrest.CoreMatchers.is; 30 | import static org.hamcrest.MatcherAssert.assertThat; 31 | 32 | /** 33 | * @author Alexey Nikolaenko alexey@tcherezov.com 34 | * Date: 30/09/2015 35 | */ 36 | public class FileDownloadServletTest extends BaseServletTest { 37 | 38 | private Server fileUploadServer; 39 | private HttpHost serverHost; 40 | 41 | @Before 42 | public void setUp() throws Exception { 43 | fileUploadServer = startServerForServlet(new FileDownloadServlet(), "/" + FileDownloadServlet.class.getSimpleName() + "/*"); 44 | serverHost = new HttpHost("localhost", ((AbstractNetworkConnector) fileUploadServer.getConnectors()[0]).getLocalPort()); 45 | } 46 | 47 | @After 48 | public void tearDown() throws Exception { 49 | fileUploadServer.stop(); 50 | } 51 | 52 | @Test 53 | public void getShouldReturnFileContentsWithNameInHeader() throws IOException { 54 | File fileToGet = File.createTempFile("test filename with spaces", ".txt"); 55 | FileUtils.write(fileToGet, "expected_content", StandardCharsets.UTF_8); 56 | 57 | CloseableHttpClient httpClient = HttpClients.createDefault(); 58 | 59 | String encode = Base64.getUrlEncoder().encodeToString(fileToGet.getAbsolutePath().getBytes(StandardCharsets.UTF_8)); 60 | HttpGet httpGet = new HttpGet("/FileDownloadServlet/" + encode); 61 | 62 | CloseableHttpResponse execute = httpClient.execute(serverHost, httpGet); 63 | 64 | //check contents are properly sent 65 | try ( 66 | InputStream content = execute.getEntity().getContent()) { 67 | String s = IOUtils.toString(content, StandardCharsets.UTF_8); 68 | assertThat(s, is("expected_content")); 69 | } 70 | 71 | //check file name is available from header 72 | Header contentDispositionHeader = execute.getFirstHeader(HttpHeaders.CONTENT_DISPOSITION); 73 | assertThat(contentDispositionHeader.getValue(), containsString("filename=" + fileToGet.getName())); 74 | //check file is not locked by anything 75 | assertTrue(fileToGet.delete()); 76 | } 77 | 78 | @Test 79 | public void getShouldReturnBadRequestWhenFileNotExists() throws IOException { 80 | CloseableHttpClient httpClient = HttpClients.createDefault(); 81 | 82 | String encode = Base64.getUrlEncoder().encodeToString("/some/location/".getBytes(StandardCharsets.UTF_8)); 83 | HttpGet httpGet = new HttpGet("/FileDownloadServlet/" + encode); 84 | 85 | CloseableHttpResponse execute = httpClient.execute(serverHost, httpGet); 86 | 87 | int statusCode = execute.getStatusLine().getStatusCode(); 88 | assertThat(statusCode, is(HttpStatus.SC_BAD_REQUEST)); 89 | 90 | //check error message is set 91 | try ( 92 | InputStream content = execute.getEntity().getContent()) { 93 | String s = IOUtils.toString(content, StandardCharsets.UTF_8); 94 | assertThat(s, is("Requested file doesn't exist.")); 95 | } 96 | } 97 | 98 | @Test 99 | public void getShouldReturnBadRequestWhenFileIsDirectory() throws IOException { 100 | File directory = Files.createTempDir(); 101 | CloseableHttpClient httpClient = HttpClients.createDefault(); 102 | 103 | String encode = Base64.getUrlEncoder().encodeToString(directory.getAbsolutePath().getBytes(StandardCharsets.UTF_8)); 104 | HttpGet httpGet = new HttpGet("/FileDownloadServlet/" + encode); 105 | 106 | CloseableHttpResponse execute = httpClient.execute(serverHost, httpGet); 107 | 108 | int statusCode = execute.getStatusLine().getStatusCode(); 109 | assertThat(statusCode, is(HttpStatus.SC_BAD_REQUEST)); 110 | 111 | //check error message is set 112 | try ( 113 | InputStream content = execute.getEntity().getContent()) { 114 | String s = IOUtils.toString(content, StandardCharsets.UTF_8); 115 | assertThat(s, is("Requested file is directory.")); 116 | } 117 | //check file is not locked by anything 118 | assertTrue(directory.delete()); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /node-extensions/file-extension/src/test/java/io/sterodium/extensions/node/upload/FileUploadServletTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.node.upload; 2 | 3 | 4 | import io.sterodium.extensions.node.BaseServletTest; 5 | import com.google.common.net.HttpHeaders; 6 | import com.google.common.net.MediaType; 7 | import org.apache.commons.io.IOUtils; 8 | import org.apache.http.StatusLine; 9 | import org.apache.http.client.methods.CloseableHttpResponse; 10 | import org.apache.http.client.methods.HttpPost; 11 | import org.apache.http.entity.InputStreamEntity; 12 | import org.apache.http.impl.client.CloseableHttpClient; 13 | import org.apache.http.impl.client.HttpClients; 14 | import org.junit.After; 15 | import org.junit.Before; 16 | import org.junit.Test; 17 | import org.seleniumhq.jetty9.server.AbstractNetworkConnector; 18 | import org.seleniumhq.jetty9.server.Server; 19 | 20 | import java.io.File; 21 | import java.io.FileInputStream; 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.nio.charset.StandardCharsets; 25 | 26 | import static org.hamcrest.CoreMatchers.equalTo; 27 | import static org.hamcrest.CoreMatchers.is; 28 | import static org.junit.Assert.assertThat; 29 | 30 | /** 31 | * @author Alexey Nikolaenko alexey@tcherezov.com 32 | * Date: 24/09/2015 33 | */ 34 | public class FileUploadServletTest extends BaseServletTest { 35 | 36 | private int port; 37 | private File zipArchive; 38 | private Server fileUploadServer; 39 | private File unzippedFile; 40 | private File unzippedArchive; 41 | 42 | @Before 43 | public void setUp() throws Exception { 44 | fileUploadServer = startServerForServlet(new FileUploadServlet(), "/" + FileUploadServlet.class.getSimpleName() + "/*"); 45 | port = ((AbstractNetworkConnector) fileUploadServer.getConnectors()[0]).getLocalPort(); 46 | 47 | zipArchive = createZipArchiveWithTextFile(); 48 | } 49 | 50 | @Test 51 | public void testDoPost() throws IOException { 52 | CloseableHttpClient httpClient = HttpClients.createDefault(); 53 | 54 | HttpPost httpPost = new HttpPost("http://localhost:" + port + "/FileUploadServlet/"); 55 | httpPost.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.OCTET_STREAM.toString()); 56 | 57 | FileInputStream fileInputStream = new FileInputStream(zipArchive); 58 | InputStreamEntity entity = new InputStreamEntity(fileInputStream); 59 | httpPost.setEntity(entity); 60 | 61 | CloseableHttpResponse execute = httpClient.execute(httpPost); 62 | 63 | StatusLine statusLine = execute.getStatusLine(); 64 | assertThat(statusLine.getStatusCode(), equalTo(200)); 65 | 66 | try ( 67 | InputStream content = execute.getEntity().getContent()) { 68 | String directory = IOUtils.toString(content, StandardCharsets.UTF_8); 69 | unzippedArchive = new File(directory); 70 | unzippedFile = new File(directory + "/" + ZIP_FILE_NAME); 71 | } 72 | 73 | assertThat(unzippedFile.exists(), is(true)); 74 | 75 | try (FileInputStream unzippedFileStream = new FileInputStream(unzippedFile)) { 76 | String contents = IOUtils.toString(unzippedFileStream, StandardCharsets.UTF_8); 77 | assertThat(contents, is("test data")); 78 | } 79 | } 80 | 81 | @After 82 | public void tearDown() throws Exception { 83 | deleteIfExists(unzippedFile, unzippedArchive, zipArchive); 84 | fileUploadServer.stop(); 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /node-extensions/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | selenium-grid-extensions 5 | io.sterodium 6 | 1.1-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | node-extensions 11 | pom 12 | 13 | Node extensions 14 | Extensions for Selenium Node 15 | 16 | 17 | file-extension 18 | sikuli-extension 19 | all-node-extensions 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /node-extensions/sikuli-extension/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | node-extensions 5 | io.sterodium 6 | 1.1-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | sikuli-extension 11 | Sikuli Extension 12 | 13 | 14 | 15 | io.sterodium 16 | sterodium-rmi 17 | 18 | 19 | org.seleniumhq.selenium 20 | selenium-server 21 | provided 22 | 23 | 24 | 25 | 26 | org.sikuli 27 | sikuli-api 28 | 29 | 30 | org.bytedeco.javacpp-presets 31 | opencv 32 | 33 | 34 | org.bytedeco.javacpp-presets 35 | opencv 36 | linux-x86 37 | 38 | 39 | org.bytedeco.javacpp-presets 40 | opencv 41 | linux-x86_64 42 | 43 | 44 | org.bytedeco.javacpp-presets 45 | opencv 46 | windows-x86_64 47 | 48 | 49 | org.bytedeco.javacpp-presets 50 | opencv 51 | windows-x86 52 | 53 | 54 | org.bytedeco.javacpp-presets 55 | opencv 56 | macosx-x86_64 57 | 58 | 59 | 60 | 61 | junit 62 | junit 63 | test 64 | 65 | 66 | org.mockito 67 | mockito-all 68 | test 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /node-extensions/sikuli-extension/src/main/java/io/sterodium/extensions/node/SikuliExtensionServlet.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.node; 2 | 3 | import com.google.gson.Gson; 4 | import io.sterodium.extensions.node.rmi.SikuliApplication; 5 | import io.sterodium.rmi.protocol.MethodInvocationDto; 6 | import io.sterodium.rmi.protocol.MethodInvocationResultDto; 7 | import org.openqa.grid.internal.GridRegistry; 8 | import org.openqa.grid.web.servlet.RegistryBasedServlet; 9 | 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | import java.util.regex.Matcher; 15 | import java.util.regex.Pattern; 16 | 17 | /** 18 | * @author Alexey Nikolaenko alexey@tcherezov.com 19 | * Date: 20/09/2015 20 | */ 21 | public class SikuliExtensionServlet extends RegistryBasedServlet { 22 | 23 | private static final Gson GSON = new Gson(); 24 | 25 | private static final SikuliApplication SIKULI_APPLICATION = new SikuliApplication(); 26 | 27 | public SikuliExtensionServlet() { 28 | this(null); 29 | } 30 | 31 | public SikuliExtensionServlet(GridRegistry registry) { 32 | super(registry); 33 | } 34 | 35 | @Override 36 | protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 37 | String objectId = getObjectId(req); 38 | if (objectId == null) { 39 | resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Can't find object ID in URL string"); 40 | return; 41 | } 42 | MethodInvocationDto method = GSON.fromJson(req.getReader(), MethodInvocationDto.class); 43 | MethodInvocationResultDto result = SIKULI_APPLICATION.invoke(objectId, method); 44 | resp.getWriter().write(GSON.toJson(result)); 45 | } 46 | 47 | private String getObjectId(HttpServletRequest req) { 48 | String requestURI = req.getRequestURI(); 49 | Pattern pattern = Pattern.compile(".+/([^/]+)"); 50 | Matcher matcher = pattern.matcher(requestURI); 51 | if (!matcher.matches()) { 52 | return null; 53 | } 54 | return matcher.group(1); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /node-extensions/sikuli-extension/src/main/java/io/sterodium/extensions/node/rmi/SikuliApplication.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.node.rmi; 2 | 3 | import io.sterodium.rmi.protocol.MethodInvocationDto; 4 | import io.sterodium.rmi.protocol.MethodInvocationResultDto; 5 | import io.sterodium.rmi.protocol.server.RmiFacade; 6 | import org.sikuli.api.DesktopScreenRegion; 7 | import org.sikuli.api.robot.desktop.DesktopKeyboard; 8 | import org.sikuli.api.robot.desktop.DesktopMouse; 9 | 10 | import java.awt.Toolkit; 11 | import java.util.logging.Level; 12 | import java.util.logging.Logger; 13 | 14 | /** 15 | * @author Mihails Volkovs mihails.volkovs@gmail.com 16 | * Date: 24/09/2015 17 | */ 18 | public class SikuliApplication { 19 | 20 | private static final Logger LOGGER = Logger.getLogger(SikuliApplication.class.getName()); 21 | 22 | private final RmiFacade rmiFacade; 23 | 24 | public SikuliApplication() { 25 | 26 | // base Sikuli operations 27 | rmiFacade = new RmiFacade(); 28 | rmiFacade.add("mouse", new DesktopMouse()); 29 | rmiFacade.add("keyboard", new DesktopKeyboard()); 30 | try { 31 | rmiFacade.add("desktop", new DesktopScreenRegion()); 32 | rmiFacade.add("clipboard", Toolkit.getDefaultToolkit().getSystemClipboard()); 33 | } catch (ExceptionInInitializerError e) { 34 | LOGGER.log(Level.SEVERE, "Sikuli operations are not available on this environment.", e); 35 | } 36 | rmiFacade.add("target-factory", new TargetFactory()); 37 | } 38 | 39 | public MethodInvocationResultDto invoke(String objectId, MethodInvocationDto invocation) { 40 | return rmiFacade.invoke(objectId, invocation); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /node-extensions/sikuli-extension/src/main/java/io/sterodium/extensions/node/rmi/TargetFactory.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.node.rmi; 2 | 3 | import com.google.common.annotations.VisibleForTesting; 4 | import com.google.common.base.Predicate; 5 | import org.apache.commons.io.filefilter.NameFileFilter; 6 | import org.sikuli.api.ImageTarget; 7 | 8 | import java.io.File; 9 | import java.util.Arrays; 10 | import java.util.Collection; 11 | 12 | import static com.google.common.base.Preconditions.checkState; 13 | import static com.google.common.collect.Collections2.filter; 14 | import static org.apache.commons.io.FileUtils.listFiles; 15 | import static org.apache.commons.io.FileUtils.listFilesAndDirs; 16 | import static org.apache.commons.io.filefilter.DirectoryFileFilter.DIRECTORY; 17 | import static org.apache.commons.io.filefilter.TrueFileFilter.TRUE; 18 | 19 | /** 20 | * @author Mihails Volkovs mihails.volkovs@gmail.com 21 | * Date: 20/09/2015 22 | */ 23 | public class TargetFactory { 24 | 25 | private File folderToScan = new File("."); 26 | 27 | public void setImagePrefix(String imagePathPrefix) { 28 | String imageFolder = !imagePathPrefix.endsWith("/") ? imagePathPrefix + "/" : imagePathPrefix; 29 | this.folderToScan = new File(imageFolder); 30 | checkState(folderToScan.exists(), "Folder %s does not exist", folderToScan.getAbsolutePath()); 31 | checkState(folderToScan.isDirectory(), "Folder %s is not a folder", folderToScan.getAbsolutePath()); 32 | } 33 | 34 | public ImageTarget createImageTarget(String imageFile) { 35 | return new ImageTarget(findImageFile(imageFile)); 36 | } 37 | 38 | @VisibleForTesting 39 | protected File findImageFile(String imageFile) { 40 | File foundImageFile = findImageFile(folderToScan, imageFile); 41 | // creating non existing file to avoid NPE 42 | return foundImageFile == null ? new File(imageFile) : foundImageFile; 43 | } 44 | 45 | private File findImageFile(File lookupFolder, String lookupTarget) { 46 | // given file can contain sub-folders in its name 47 | String[] paths = lookupTarget.split("[/\\\\]"); 48 | String[] foldersToFind = Arrays.copyOfRange(paths, 0, paths.length - 1); 49 | 50 | for (String folderToFind : foldersToFind) { 51 | lookupFolder = findFolderRecursively(lookupFolder, folderToFind); 52 | if (lookupFolder == null) { 53 | return null; 54 | } 55 | } 56 | lookupTarget = paths[paths.length - 1]; 57 | 58 | // finally searching for file name recursively 59 | Collection files = listFiles(lookupFolder, new NameFileFilter(lookupTarget), TRUE); 60 | return files.isEmpty() ? null : files.iterator().next(); 61 | } 62 | 63 | private File findFolderRecursively(File folderToScan, final String folderToFind) { 64 | // filtering out empty arguments 65 | if (folderToFind.isEmpty()) { 66 | return folderToScan; 67 | } 68 | 69 | // collecting all the sub-folders recursively 70 | Collection foundFolders = listFilesAndDirs(folderToScan, DIRECTORY, TRUE); 71 | 72 | // filtering all the folders by folder name 73 | foundFolders = filter(foundFolders, new Predicate() { 74 | @Override 75 | public boolean apply(File file) { 76 | String fileName = file.getAbsolutePath(); 77 | return fileName.endsWith("/" + folderToFind) || fileName.endsWith("\\" + folderToFind); 78 | } 79 | }); 80 | return foundFolders.isEmpty() ? null : foundFolders.iterator().next(); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /node-extensions/sikuli-extension/src/test/java/io/sterodium/extensions/node/SikuliExtensionServletTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.node; 2 | 3 | import com.google.gson.Gson; 4 | import io.sterodium.rmi.protocol.MethodInvocationDto; 5 | import org.apache.http.HttpHost; 6 | import org.apache.http.HttpResponse; 7 | import org.apache.http.HttpStatus; 8 | import org.apache.http.client.methods.HttpPost; 9 | import org.apache.http.entity.StringEntity; 10 | import org.apache.http.impl.client.CloseableHttpClient; 11 | import org.apache.http.impl.client.HttpClients; 12 | import org.apache.http.util.EntityUtils; 13 | import org.junit.After; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | import org.seleniumhq.jetty9.server.AbstractNetworkConnector; 17 | import org.seleniumhq.jetty9.server.Server; 18 | import org.seleniumhq.jetty9.servlet.ServletContextHandler; 19 | import org.seleniumhq.jetty9.servlet.ServletHolder; 20 | 21 | import javax.servlet.http.HttpServlet; 22 | import java.io.IOException; 23 | import java.net.URLEncoder; 24 | 25 | import static org.hamcrest.CoreMatchers.containsString; 26 | import static org.hamcrest.CoreMatchers.is; 27 | import static org.junit.Assert.assertThat; 28 | 29 | public class SikuliExtensionServletTest { 30 | 31 | private Server sikuliServer; 32 | private HttpHost serverHost; 33 | private String basePath; 34 | 35 | @Before 36 | public void setUp() throws Exception { 37 | basePath = "/" + SikuliExtensionServlet.class.getSimpleName() + "/"; 38 | sikuliServer = startServerForServlet(new SikuliExtensionServlet(), basePath + "*"); 39 | 40 | serverHost = new HttpHost("localhost", ((AbstractNetworkConnector) sikuliServer.getConnectors()[0]).getLocalPort()); 41 | } 42 | 43 | @After 44 | public void tearDown() throws Exception { 45 | sikuliServer.stop(); 46 | } 47 | 48 | @Test 49 | public void shouldMakeSimpleRemoteInvocation() throws IOException { 50 | String invocationJsonString = invocationJsonString(); 51 | 52 | CloseableHttpClient httpClient = HttpClients.createDefault(); 53 | 54 | HttpPost request = new HttpPost(basePath + "/" + URLEncoder.encode("target-factory", "UTF-8")); 55 | request.setEntity(new StringEntity(invocationJsonString)); 56 | 57 | HttpResponse httpResponse = httpClient.execute(serverHost, request); 58 | 59 | int statusCode = httpResponse.getStatusLine().getStatusCode(); 60 | 61 | assertThat(statusCode, is(HttpStatus.SC_OK)); 62 | assertThat(EntityUtils.toString(httpResponse.getEntity()), is("{}")); 63 | } 64 | 65 | @Test 66 | public void shouldReturnErrorInformation() throws IOException { 67 | String invocationJsonString = failingInvocationJsonString(); 68 | 69 | CloseableHttpClient httpClient = HttpClients.createDefault(); 70 | 71 | HttpPost request = new HttpPost(basePath + "/" + URLEncoder.encode("mouse", "UTF-8")); 72 | request.setEntity(new StringEntity(invocationJsonString)); 73 | 74 | HttpResponse httpResponse = httpClient.execute(serverHost, request); 75 | 76 | int statusCode = httpResponse.getStatusLine().getStatusCode(); 77 | 78 | assertThat(statusCode, is(HttpStatus.SC_OK)); 79 | assertThat(EntityUtils.toString(httpResponse.getEntity()), containsString("notExistingMethod not found")); 80 | } 81 | 82 | @Test 83 | public void shouldFailIfObjectIdNotPresent() throws IOException { 84 | String invocationJsonString = failingInvocationJsonString(); 85 | 86 | 87 | CloseableHttpClient httpClient = HttpClients.createDefault(); 88 | 89 | HttpPost request = new HttpPost(basePath + "/"); 90 | request.setEntity(new StringEntity(invocationJsonString)); 91 | 92 | HttpResponse httpResponse = httpClient.execute(serverHost, request); 93 | 94 | int statusCode = httpResponse.getStatusLine().getStatusCode(); 95 | 96 | assertThat(statusCode, is(HttpStatus.SC_BAD_REQUEST)); 97 | } 98 | 99 | private String invocationJsonString() { 100 | String[] argClasses = {String.class.getName()}; 101 | String[] arguments = {"target"}; 102 | MethodInvocationDto invocation = new MethodInvocationDto("setImagePrefix", argClasses, arguments); 103 | 104 | return new Gson().toJson(invocation); 105 | } 106 | 107 | private String failingInvocationJsonString() { 108 | String[] argClasses = {String.class.getName()}; 109 | String[] arguments = {"value"}; 110 | MethodInvocationDto invocation = new MethodInvocationDto("notExistingMethod", argClasses, arguments); 111 | 112 | return new Gson().toJson(invocation); 113 | } 114 | 115 | protected Server startServerForServlet(HttpServlet servlet, String path) throws Exception { 116 | Server server = new Server(0); 117 | 118 | ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); 119 | context.setContextPath("/"); 120 | server.setHandler(context); 121 | 122 | context.addServlet(new ServletHolder(servlet), path); 123 | server.start(); 124 | 125 | return server; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /node-extensions/sikuli-extension/src/test/java/io/sterodium/extensions/node/rmi/TargetFactoryTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.node.rmi; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.io.File; 7 | 8 | import static junit.framework.TestCase.fail; 9 | import static org.hamcrest.CoreMatchers.containsString; 10 | import static org.hamcrest.MatcherAssert.assertThat; 11 | import static org.junit.Assert.assertFalse; 12 | import static org.junit.Assert.assertTrue; 13 | 14 | /** 15 | * @author Mihails Volkovs mihails.volkovs@gmail.com 16 | * Date: 20/09/2015 17 | */ 18 | public class TargetFactoryTest { 19 | 20 | public static final String NODE_FOLDER = "src/main/java/io/sterodium/extensions/node"; 21 | 22 | public static final String FILE_PATH = "rmi/TargetFactory.java"; 23 | public static final String FILE_PATH_BACKSLASHED = "rmi\\TargetFactory.java"; 24 | 25 | private TargetFactory targetFactory; 26 | 27 | @Before 28 | public void setUp() { 29 | targetFactory = new TargetFactory(); 30 | targetFactory.setImagePrefix(NODE_FOLDER); 31 | } 32 | 33 | @Test 34 | public void setImagePrefixWithFile() { 35 | try { 36 | targetFactory.setImagePrefix(NODE_FOLDER + "/" + FILE_PATH); 37 | fail(); 38 | } catch (IllegalStateException e) { 39 | assertThat(e.getMessage(), containsString("is not a folder")); 40 | } 41 | } 42 | 43 | @Test 44 | public void setImagePrefixWithNonExistingFolder() { 45 | try { 46 | targetFactory.setImagePrefix(NODE_FOLDER + "_NON_EXISTING"); 47 | fail(); 48 | } catch (IllegalStateException e) { 49 | assertThat(e.getMessage(), containsString("does not exist")); 50 | } 51 | } 52 | 53 | @Test 54 | public void shouldImageFindFile_FullPath() { 55 | // user is very accurate and precise 56 | File f1 = targetFactory.findImageFile(FILE_PATH); 57 | assertTrue(f1.exists()); 58 | 59 | File f2 = targetFactory.findImageFile(FILE_PATH_BACKSLASHED); 60 | assertTrue(f2.exists()); 61 | } 62 | 63 | @Test 64 | public void shouldFindFile_SlashAtFullPath() { 65 | // user mistakenly added "/" in front of relative file name 66 | File f1 = targetFactory.findImageFile("/" + FILE_PATH); 67 | assertTrue(f1.exists()); 68 | 69 | File f2 = targetFactory.findImageFile("\\" + FILE_PATH_BACKSLASHED); 70 | assertTrue(f2.exists()); 71 | } 72 | 73 | @Test 74 | public void shouldFindFile_MissingFoldersFromFullPath() { 75 | // user forgets some folders from full file path 76 | File f1 = targetFactory.findImageFile("rmi/TargetFactory.java"); 77 | assertTrue(f1.exists()); 78 | 79 | File f2 = targetFactory.findImageFile("rmi\\TargetFactory.java"); 80 | assertTrue(f2.exists()); 81 | 82 | } 83 | 84 | @Test 85 | public void shouldFindFile_RandomFolderFromFullFilePath() { 86 | targetFactory.setImagePrefix("src/test/resources"); 87 | // user selects random folder from full file path 88 | File f1 = targetFactory.findImageFile("level1/search_goal.txt"); 89 | assertTrue(f1.exists()); 90 | // user selects random folder from full file path 91 | File f2 = targetFactory.findImageFile("level2\\search_goal.txt"); 92 | assertTrue(f2.exists()); 93 | } 94 | 95 | @Test 96 | public void shouldNotFindFile_WhenNotImageFile() { 97 | File f1 = targetFactory.findImageFile("sikuli"); 98 | assertFalse(f1.exists()); 99 | // user selects random folder from full file path 100 | File f2 = targetFactory.findImageFile("sikuli/rmi"); 101 | assertFalse(f2.exists()); 102 | } 103 | 104 | @Test 105 | public void shouldFindFile_ByFileName() { 106 | // user can also search just by file name 107 | File f1 = targetFactory.findImageFile("TargetFactory.java"); 108 | assertTrue(f1.exists()); 109 | } 110 | 111 | @Test 112 | public void shouldNotFindFile_FileNameWrong() { 113 | // wrong file - nothing is found 114 | File f1 = targetFactory.findImageFile("NonExisting.java"); 115 | assertFalse(f1.exists()); 116 | 117 | // wrong folder - nothing is found 118 | f1 = targetFactory.findImageFile("NON_EXISTING_FOLDER/TargetFactory.java"); 119 | assertFalse(f1.exists()); 120 | 121 | 122 | // wrong folder - nothing is found 123 | f1 = targetFactory.findImageFile("NON_EXISTING_FOLDER\\TargetFactory.java"); 124 | assertFalse(f1.exists()); 125 | 126 | // similar folder - nothing is found 127 | f1 = targetFactory.findImageFile("ikuli/TargetFactory.java"); 128 | assertFalse(f1.exists()); 129 | 130 | // similar folder - nothing is found 131 | f1 = targetFactory.findImageFile("ikuli\\TargetFactory.java"); 132 | assertFalse(f1.exists()); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /node-extensions/sikuli-extension/src/test/resources/level1/level2/level3/search_goal.txt: -------------------------------------------------------------------------------- 1 | success 2 | -------------------------------------------------------------------------------- /sample/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | selenium-grid-extensions 7 | io.sterodium 8 | 0.7-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | sample 13 | 14 | 15 | 1.4.1 16 | 1.8 17 | 1.7.12 18 | 19 | 20 | 21 | 22 | 23 | 24 | org.apache.maven.plugins 25 | maven-enforcer-plugin 26 | ${plugin.enforcer.version} 27 | 28 | 29 | pre-integration-test 30 | enforce 31 | 32 | 33 | 34 | webdriver.gecko.driver 35 | [ERRROR] You must set webdriver.gecko.driver property! 36 | 37 | 38 | ${webdriver.gecko.driver} 39 | [ERROR] Missing webdriver executable ${webdriver.gecko.driver} 40 | 41 | 42 | true 43 | 44 | 45 | 46 | 47 | 48 | org.apache.maven.plugins 49 | maven-antrun-plugin 50 | ${plugin.antrun.version} 51 | 52 | 53 | test-compile 54 | run 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | org.seleniumhq.selenium 69 | selenium-server 70 | 71 | 72 | io.sterodium 73 | extension-proxy 74 | ${project.version} 75 | 76 | 77 | io.sterodium 78 | all-node-extensions 79 | ${project.version} 80 | 81 | 82 | io.sterodium 83 | sikuli-extension-client 84 | ${project.version} 85 | test 86 | 87 | 88 | com.codeborne 89 | selenide 90 | 4.0 91 | test 92 | 93 | 94 | org.slf4j 95 | slf4j-log4j12 96 | ${slf4j.version} 97 | 98 | 99 | junit 100 | junit 101 | 4.13.1 102 | test 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /sample/src/main/java/io/sterodium/sample/LocalSeleniumGridLauncher.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.sample; 2 | 3 | import com.google.common.io.Resources; 4 | import org.openqa.grid.selenium.GridLauncherV3; 5 | 6 | import java.io.File; 7 | import java.net.URL; 8 | 9 | /** 10 | * @author Alexey Nikolaenko alexey@tcherezov.com 11 | * Date: 24/11/2015 12 | */ 13 | public final class LocalSeleniumGridLauncher { 14 | 15 | private static final String LOG_DIR = "target/selenium-logs"; 16 | private static final String HUB_PARAMS = "-role hub -hubConfig %s"; 17 | private static final String NODE_PARAMS = "-role node -nodeConfig %s"; 18 | 19 | public static void main(String[] args) throws Exception { 20 | launchGrid(); 21 | } 22 | 23 | private static String getConfigPath(String configName) { 24 | URL resource = Resources.getResource(configName); 25 | return resource.getPath(); 26 | } 27 | 28 | public static void launchGrid() throws Exception { 29 | new File(LOG_DIR).mkdirs(); 30 | 31 | String[] hubParams = String.format(HUB_PARAMS, getConfigPath("hubConfig.json")).split(" "); 32 | GridLauncherV3.main(hubParams); 33 | System.out.println("Hub started"); 34 | 35 | String[] nodeParams = String.format(NODE_PARAMS, getConfigPath("nodeConfig.json")).split(" "); 36 | GridLauncherV3.main(nodeParams); 37 | System.out.println("Node started"); 38 | } 39 | 40 | private LocalSeleniumGridLauncher() { 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /sample/src/main/resources/hubConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "host": localhost, 3 | "port": 4444, 4 | "newSessionWaitTimeout": -1, 5 | "servlets": [ 6 | "io.sterodium.extensions.hub.proxy.HubRequestsProxyingServlet" 7 | ], 8 | "prioritizer": null, 9 | "capabilityMatcher": "io.sterodium.extensions.capability.CustomCapabilityMatcher", 10 | "throwOnCapabilityNotPresent": true, 11 | "nodePolling": 5000, 12 | "cleanUpCycle": 5000, 13 | "timeout": 300000, 14 | "browserTimeout": 0, 15 | "maxSession": 5, 16 | "jettyMaxThreads": -1 17 | } 18 | -------------------------------------------------------------------------------- /sample/src/main/resources/nodeConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "capabilities": [ 3 | { 4 | "browserName": "firefox", 5 | "maxInstances": 1, 6 | "extension.sikuliCapability": true 7 | } 8 | ], 9 | "proxy": "org.openqa.grid.selenium.proxy.DefaultRemoteProxy", 10 | "maxSession": 1, 11 | "port": 5555, 12 | "register": true, 13 | "registerCycle": 5000, 14 | "hub": "http://localhost:4444", 15 | "servlets": [ 16 | "io.sterodium.extensions.node.SikuliExtensionServlet", 17 | "io.sterodium.extensions.node.upload.FileUploadServlet", 18 | "io.sterodium.extensions.node.download.FileDownloadServlet" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /sample/src/test/java/io/sterodium/sample/test/AbstractTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.sample.test; 2 | 3 | import com.codeborne.selenide.WebDriverRunner; 4 | import com.google.common.io.Resources; 5 | import org.junit.After; 6 | import org.junit.Before; 7 | import org.openqa.selenium.Platform; 8 | import org.openqa.selenium.remote.DesiredCapabilities; 9 | import org.openqa.selenium.remote.RemoteWebDriver; 10 | 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.net.URL; 14 | import java.util.Properties; 15 | 16 | /** 17 | * @author Alexey Nikolaenko alexey@tcherezov.com 18 | * Date: 24/11/2015 19 | */ 20 | abstract class AbstractTest { 21 | protected String host; 22 | protected int port; 23 | 24 | @Before 25 | public void setUp() throws IOException { 26 | readGridHostPort(); 27 | 28 | DesiredCapabilities desiredCapabilities = firefoxWithSikuli(); 29 | URL url = new URL(String.format("http://%s:%d/wd/hub", host, port)); 30 | 31 | RemoteWebDriver remoteWebDriver = new RemoteWebDriver(url, desiredCapabilities); 32 | WebDriverRunner.setWebDriver(remoteWebDriver); 33 | } 34 | 35 | private void readGridHostPort() throws IOException { 36 | URL resource = Resources.getResource("grid.properties"); 37 | Properties properties = new Properties(); 38 | try (InputStream i = resource.openStream()) { 39 | properties.load(i); 40 | } 41 | 42 | host = properties.get("grid.host").toString(); 43 | port = Integer.parseInt(properties.get("grid.port").toString()); 44 | } 45 | 46 | @After 47 | public void tearDown() { 48 | WebDriverRunner.closeWebDriver(); 49 | } 50 | 51 | private DesiredCapabilities firefoxWithSikuli() { 52 | DesiredCapabilities desiredCapabilities = new DesiredCapabilities(); 53 | desiredCapabilities.setBrowserName("firefox"); 54 | desiredCapabilities.setPlatform(Platform.ANY); 55 | desiredCapabilities.setCapability("sikuliExtension", true); 56 | return desiredCapabilities; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /sample/src/test/java/io/sterodium/sample/test/AceEditorTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.sample.test; 2 | 3 | import com.codeborne.selenide.WebDriverRunner; 4 | import io.sterodium.extensions.client.SikuliExtensionClient; 5 | import io.sterodium.sample.test.ui.SikuliHelper; 6 | import io.sterodium.sample.test.ui.TextBox; 7 | import org.junit.Test; 8 | import org.openqa.selenium.remote.RemoteWebDriver; 9 | 10 | import java.awt.event.KeyEvent; 11 | 12 | import static com.codeborne.selenide.Selenide.open; 13 | 14 | /** 15 | * @author Alexey Nikolaenko alexey@tcherezov.com 16 | * Date: 24/11/2015 17 | */ 18 | public class AceEditorTest extends AbstractTest { 19 | 20 | private static final String ACE_IMAGES_BUNDLE = "images/ace"; 21 | private static final String ACE_EDITOR_URL = "https://ace.c9.io/build/kitchen-sink.html"; 22 | private static final String JS_FUNC = "function printSomethingAwesome() {alert(\"Sterodium.io\");}"; 23 | 24 | @Test 25 | public void inputFunctionToAceEditor() throws InterruptedException { 26 | open(ACE_EDITOR_URL); 27 | maximize(); 28 | 29 | String sessionId = getSessionId(); 30 | 31 | SikuliExtensionClient sikuliExtensionClient = new SikuliExtensionClient(host, port, sessionId); 32 | sikuliExtensionClient.uploadResourceBundle(ACE_IMAGES_BUNDLE); 33 | 34 | 35 | SikuliHelper sikuliHelper = new SikuliHelper(sikuliExtensionClient); 36 | 37 | TextBox editor = sikuliHelper.findTextBox("js_body.png"); 38 | editor.click(); 39 | editor.deleteAllText(); 40 | 41 | editor.press(KeyEvent.VK_ENTER); 42 | 43 | editor.write(JS_FUNC); 44 | 45 | editor.press(KeyEvent.VK_ENTER); 46 | } 47 | 48 | 49 | private String getSessionId() { 50 | return ((RemoteWebDriver) WebDriverRunner.getWebDriver()).getSessionId().toString(); 51 | } 52 | 53 | private void maximize() { 54 | WebDriverRunner.getWebDriver().manage().window().maximize(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sample/src/test/java/io/sterodium/sample/test/GoogleSearchTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.sample.test; 2 | 3 | import com.codeborne.selenide.WebDriverRunner; 4 | import io.sterodium.extensions.client.SikuliExtensionClient; 5 | import io.sterodium.sample.test.ui.SikuliHelper; 6 | import io.sterodium.sample.test.ui.TextBox; 7 | import io.sterodium.sample.test.ui.UiComponent; 8 | import org.junit.Test; 9 | import org.openqa.selenium.remote.RemoteWebDriver; 10 | 11 | import static com.codeborne.selenide.Selenide.open; 12 | 13 | /** 14 | * @author Alexey Nikolaenko alexey@tcherezov.com 15 | * Date: 24/11/2015 16 | */ 17 | public class GoogleSearchTest extends AbstractTest { 18 | 19 | private static final String GOOGLE_IMAGES_BUNDLE = "images/google"; 20 | private static final String GOOGLE_IMAGES_URL = "https://images.google.com/"; 21 | private static final String SIKULI_LOGO_SEARCH = "sikuli logo"; 22 | 23 | private static final String SEARCH_FIELD_IMG = "search_field.png"; 24 | private static final String SEARCH_BUTTON_IMG = "search_button.png"; 25 | private static final String SIKULI_LOGO_IMG = "sikuli_logo.png"; 26 | 27 | @Test 28 | public void searchGoogleForSikuliLogo() throws InterruptedException { 29 | open(GOOGLE_IMAGES_URL); 30 | 31 | String sessionId = getSessionId(); 32 | 33 | SikuliExtensionClient sikuliExtensionClient = new SikuliExtensionClient(host, port, sessionId); 34 | sikuliExtensionClient.uploadResourceBundle(GOOGLE_IMAGES_BUNDLE); 35 | SikuliHelper sikuliHelper = new SikuliHelper(sikuliExtensionClient); 36 | 37 | TextBox searchInput = sikuliHelper.findTextBox(SEARCH_FIELD_IMG); 38 | searchInput.click(); 39 | searchInput.write(SIKULI_LOGO_SEARCH); 40 | 41 | UiComponent searchButton = sikuliHelper.find(SEARCH_BUTTON_IMG); 42 | searchButton.click(); 43 | 44 | UiComponent uiComponent = sikuliHelper.find(SIKULI_LOGO_IMG); 45 | 46 | uiComponent.click(); 47 | } 48 | 49 | private String getSessionId() { 50 | return ((RemoteWebDriver) WebDriverRunner.getWebDriver()).getSessionId().toString(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /sample/src/test/java/io/sterodium/sample/test/ui/DefaultTextBox.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.sample.test.ui; 2 | 3 | import io.sterodium.extensions.node.rmi.TargetFactory; 4 | import org.sikuli.api.ScreenRegion; 5 | import org.sikuli.api.robot.Keyboard; 6 | import org.sikuli.api.robot.Mouse; 7 | 8 | import java.awt.event.KeyEvent; 9 | 10 | /** 11 | * @author Alexey Nikolaenko alexey@tcherezov.com 12 | * Date: 24/11/2015 13 | */ 14 | public class DefaultTextBox extends DefaultUiComponent implements TextBox { 15 | 16 | private Keyboard keyboard; 17 | 18 | public DefaultTextBox(Mouse mouse, Keyboard keyboard, TargetFactory targetFactory, ScreenRegion screenRegion) { 19 | super(mouse, targetFactory, screenRegion); 20 | this.keyboard = keyboard; 21 | } 22 | 23 | public void write(String text) { 24 | keyboard.type(text); 25 | } 26 | 27 | public void press(int key) { 28 | keyboard.keyDown(key); 29 | keyboard.keyUp(key); 30 | } 31 | 32 | public void deleteAllText() { 33 | keyboard.keyDown(KeyEvent.VK_CONTROL); 34 | keyboard.keyDown(KeyEvent.VK_A); 35 | keyboard.keyUp(KeyEvent.VK_CONTROL); 36 | keyboard.keyUp(KeyEvent.VK_A); 37 | keyboard.keyDown(KeyEvent.VK_DELETE); 38 | keyboard.keyUp(KeyEvent.VK_DELETE); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /sample/src/test/java/io/sterodium/sample/test/ui/DefaultUiComponent.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.sample.test.ui; 2 | 3 | import io.sterodium.extensions.node.rmi.TargetFactory; 4 | import org.sikuli.api.ScreenRegion; 5 | import org.sikuli.api.robot.Mouse; 6 | 7 | /** 8 | * @author Alexey Nikolaenko alexey@tcherezov.com 9 | * Date: 24/11/2015 10 | */ 11 | public class DefaultUiComponent implements UiComponent { 12 | 13 | protected Mouse mouse; 14 | protected TargetFactory targetFactory; 15 | protected ScreenRegion screenRegion; 16 | 17 | DefaultUiComponent(Mouse mouse, TargetFactory targetFactory, ScreenRegion screenRegion) { 18 | this.mouse = mouse; 19 | this.targetFactory = targetFactory; 20 | this.screenRegion = screenRegion; 21 | } 22 | 23 | public void click() { 24 | mouse.click(screenRegion.getCenter()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/src/test/java/io/sterodium/sample/test/ui/ElementNotFoundException.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.sample.test.ui; 2 | 3 | /** 4 | * @author Alexey Nikolaenko alexey@tcherezov.com 5 | * Date: 24/11/2015 6 | */ 7 | public class ElementNotFoundException extends RuntimeException { 8 | public ElementNotFoundException(String s) { 9 | super(String.format("Ui element from image %s not found on the screen", s)); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sample/src/test/java/io/sterodium/sample/test/ui/SikuliHelper.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.sample.test.ui; 2 | 3 | import io.sterodium.extensions.client.SikuliExtensionClient; 4 | import io.sterodium.extensions.node.rmi.TargetFactory; 5 | import org.sikuli.api.DesktopScreenRegion; 6 | import org.sikuli.api.ImageTarget; 7 | import org.sikuli.api.ScreenRegion; 8 | import org.sikuli.api.robot.Keyboard; 9 | import org.sikuli.api.robot.Mouse; 10 | 11 | /** 12 | * @author Alexey Nikolaenko alexey@tcherezov.com 13 | * Date: 24/11/2015 14 | */ 15 | public class SikuliHelper { 16 | 17 | public static final int TIMEOUT = 6 * 1000; 18 | public static final int QUERY = 500; 19 | 20 | private SikuliExtensionClient client; 21 | private final DesktopScreenRegion desktop; 22 | private final TargetFactory targetFactory; 23 | private final Mouse mouse; 24 | private final Keyboard keyboard; 25 | 26 | public SikuliHelper(SikuliExtensionClient client) { 27 | this.client = client; 28 | desktop = client.getDesktop(); 29 | targetFactory = client.getTargetFactory(); 30 | mouse = client.getMouse(); 31 | keyboard = client.getKeyboard(); 32 | } 33 | 34 | 35 | public TextBox findTextBox(String resource) { 36 | ScreenRegion screenRegion = waitForElement(resource); 37 | return new DefaultTextBox(mouse, keyboard, targetFactory, screenRegion); 38 | } 39 | 40 | public UiComponent find(String resource) { 41 | ScreenRegion screenRegion = waitForElement(resource); 42 | return new DefaultUiComponent(mouse, targetFactory, screenRegion); 43 | } 44 | 45 | 46 | private ScreenRegion waitForElement(String resource) { 47 | ImageTarget imageTarget = targetFactory.createImageTarget(resource); 48 | 49 | ScreenRegion screenRegion = tryToFind(desktop, imageTarget); 50 | if (screenRegion != null) { 51 | return screenRegion; 52 | } 53 | throw new ElementNotFoundException(resource); 54 | 55 | } 56 | 57 | static ScreenRegion tryToFind(ScreenRegion desktop, ImageTarget imageTarget) { 58 | for (int i = 0; i < TIMEOUT / QUERY; i++) { 59 | try { 60 | ScreenRegion screenRegion = desktop.find(imageTarget); 61 | if (screenRegion != null) { 62 | return screenRegion; 63 | } 64 | Thread.sleep(QUERY); 65 | } catch (InterruptedException e) { 66 | Thread.interrupted(); 67 | } 68 | } 69 | return null; 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /sample/src/test/java/io/sterodium/sample/test/ui/TextBox.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.sample.test.ui; 2 | 3 | /** 4 | * @author Alexey Nikolaenko alexey@tcherezov.com 5 | * Date: 24/11/2015 6 | */ 7 | public interface TextBox extends UiComponent { 8 | void write(String text); 9 | 10 | void deleteAllText(); 11 | 12 | void press(int key); 13 | } 14 | -------------------------------------------------------------------------------- /sample/src/test/java/io/sterodium/sample/test/ui/UiComponent.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.sample.test.ui; 2 | 3 | /** 4 | * @author Alexey Nikolaenko alexey@tcherezov.com 5 | * Date: 24/11/2015 6 | */ 7 | public interface UiComponent { 8 | void click(); 9 | } 10 | -------------------------------------------------------------------------------- /sample/src/test/resources/grid.properties: -------------------------------------------------------------------------------- 1 | grid.host=localhost 2 | grid.port=4444 3 | -------------------------------------------------------------------------------- /sample/src/test/resources/images/ace/js_body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sterodium/selenium-grid-extensions/fef28586730b543b5086b0215b7a21463d84c084/sample/src/test/resources/images/ace/js_body.png -------------------------------------------------------------------------------- /sample/src/test/resources/images/google/search_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sterodium/selenium-grid-extensions/fef28586730b543b5086b0215b7a21463d84c084/sample/src/test/resources/images/google/search_button.png -------------------------------------------------------------------------------- /sample/src/test/resources/images/google/search_field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sterodium/selenium-grid-extensions/fef28586730b543b5086b0215b7a21463d84c084/sample/src/test/resources/images/google/search_field.png -------------------------------------------------------------------------------- /sample/src/test/resources/images/google/sikuli_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sterodium/selenium-grid-extensions/fef28586730b543b5086b0215b7a21463d84c084/sample/src/test/resources/images/google/sikuli_logo.png -------------------------------------------------------------------------------- /selenium-log4j/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | selenium-grid-extensions 5 | io.sterodium 6 | 1.1-SNAPSHOT 7 | 8 | 4.0.0 9 | 10 | selenium-log4j 11 | Selenium log4j 12 | 13 | 14 | 1.7.12 15 | 16 | 17 | 18 | 19 | io.sterodium 20 | grid-starter 21 | ${project.version} 22 | provided 23 | 24 | 25 | org.seleniumhq.selenium 26 | selenium-server 27 | provided 28 | 29 | 30 | org.slf4j 31 | slf4j-api 32 | ${slf4j.version} 33 | 34 | 35 | org.slf4j 36 | slf4j-log4j12 37 | ${slf4j.version} 38 | 39 | 40 | log4j 41 | log4j 42 | 1.2.17 43 | 44 | 45 | 46 | 47 | junit 48 | junit 49 | test 50 | 51 | 52 | org.hamcrest 53 | hamcrest-all 54 | test 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-shade-plugin 63 | 64 | 65 | package 66 | 67 | shade 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /selenium-log4j/src/main/java/io/sterodium/extensions/log/LoggingConfigurator.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.log; 2 | 3 | import io.sterodium.extensions.spi.GridConfigurator; 4 | import org.apache.commons.lang3.ArrayUtils; 5 | import org.apache.log4j.ConsoleAppender; 6 | import org.apache.log4j.Level; 7 | import org.apache.log4j.Logger; 8 | import org.apache.log4j.PatternLayout; 9 | import org.apache.log4j.RollingFileAppender; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.File; 13 | import java.util.Arrays; 14 | 15 | /** 16 | * Configures log output and log level from command line arguments if logging is not configured by 17 | * log4j.properties. If no log file is specified then logs to console. 18 | * 19 | * -log {file_path} sets log file 20 | * -debug sets log level to DEBUG (default is INFO) 21 | * 22 | * @author Vladimir Ilyin ilyin371@gmail.com 23 | * Date: 20/11/2015 24 | */ 25 | public class LoggingConfigurator implements GridConfigurator { 26 | 27 | private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(LoggingConfigurator.class); 28 | 29 | private static final String LOG_FILE_PARAM = "-log"; 30 | private static final String LOG_FILE_PROPERTY = "selenium.LOGGER"; 31 | private static final String LOG_LEVEL_PROPERTY = "selenium.LOGGER.level"; 32 | 33 | private static final String PATTERN = "%d{ISO8601} [%t] [%p] [%c] %m%n"; 34 | private static final String MAX_FILE_SIZE = "10MB"; 35 | private static final int MAX_BACKUPS = 1; 36 | 37 | private Level logLevel; 38 | 39 | @Override 40 | public String[] configure(String[] args) { 41 | 42 | boolean isLog4jConfigured = Logger.getRootLogger().getAllAppenders().hasMoreElements(); 43 | if (!isLog4jConfigured) { 44 | logLevel = Arrays.asList(args).contains("-debug") 45 | ? Level.DEBUG 46 | : getDefaultLogLevel(); 47 | 48 | String logFilename = getLogFilename(args); 49 | if (logFilename != null && !logFilename.isEmpty()) { 50 | installFileAppender(logFilename); 51 | } else { 52 | installConsoleAppender(); 53 | LOG.info("No logging configuration found, logging to console"); 54 | } 55 | } 56 | return clearLogFilenameParam(args); 57 | } 58 | 59 | 60 | private void installConsoleAppender() { 61 | ConsoleAppender console = new ConsoleAppender(); 62 | console.setName("Console"); 63 | console.setThreshold(logLevel); 64 | console.setLayout(new PatternLayout(PATTERN)); 65 | console.setFollow(true); 66 | console.activateOptions(); 67 | 68 | Logger.getRootLogger().addAppender(console); 69 | } 70 | 71 | private void installFileAppender(String logFilename) { 72 | RollingFileAppender file = new RollingFileAppender(); 73 | file.setName("FileAppender"); 74 | file.setThreshold(logLevel); 75 | file.setFile(new File(logFilename).getAbsolutePath()); 76 | file.setMaxFileSize(MAX_FILE_SIZE); 77 | file.setMaxBackupIndex(MAX_BACKUPS); 78 | file.setLayout(new PatternLayout(PATTERN)); 79 | file.setAppend(true); 80 | file.activateOptions(); 81 | 82 | Logger.getRootLogger().addAppender(file); 83 | } 84 | 85 | private static String getLogFilename(String[] args) { 86 | String logFilename; 87 | int idx = Arrays.asList(args).indexOf(LOG_FILE_PARAM); 88 | if (-1 != idx) { 89 | logFilename = args[idx + 1]; 90 | } else { 91 | logFilename = getLogFilenameFromSystemProperty(); 92 | } 93 | return logFilename; 94 | } 95 | 96 | private static String[] clearLogFilenameParam(String[] args) { 97 | System.clearProperty(LOG_FILE_PROPERTY); 98 | int idx = Arrays.asList(args).indexOf(LOG_FILE_PARAM); 99 | if (-1 != idx) { 100 | if (args.length > idx + 1) 101 | args = ArrayUtils.remove(args, idx + 1); // value 102 | args = ArrayUtils.remove(args, idx); // name 103 | } 104 | return args; 105 | } 106 | 107 | private static String getLogFilenameFromSystemProperty() { 108 | final String logFilename; 109 | 110 | logFilename = System.getProperty(LOG_FILE_PROPERTY); 111 | if (null == logFilename) { 112 | return null; 113 | } else { 114 | return new File(logFilename).getAbsolutePath(); 115 | } 116 | } 117 | 118 | private static Level getDefaultLogLevel() { 119 | final String logLevelProperty = System.getProperty(LOG_LEVEL_PROPERTY); 120 | if (null == logLevelProperty) { 121 | return Level.INFO; 122 | } else { 123 | return Level.toLevel(logLevelProperty); 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /selenium-log4j/src/main/resources/META-INF/services/io.sterodium.extensions.spi.GridConfigurator: -------------------------------------------------------------------------------- 1 | io.sterodium.extensions.log.LoggingConfigurator -------------------------------------------------------------------------------- /selenium-log4j/src/test/java/io/sterodium/extensions/log/LoggingConfiguratorTest.java: -------------------------------------------------------------------------------- 1 | package io.sterodium.extensions.log; 2 | 3 | import org.apache.log4j.Appender; 4 | import org.apache.log4j.ConsoleAppender; 5 | import org.apache.log4j.Logger; 6 | import org.apache.log4j.RollingFileAppender; 7 | import org.junit.After; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.slf4j.bridge.SLF4JBridgeHandler; 11 | 12 | import java.util.Enumeration; 13 | import java.util.LinkedList; 14 | import java.util.List; 15 | 16 | import static org.hamcrest.CoreMatchers.endsWith; 17 | import static org.hamcrest.CoreMatchers.instanceOf; 18 | import static org.hamcrest.core.Is.is; 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertNotNull; 21 | import static org.junit.Assert.assertThat; 22 | 23 | 24 | public class LoggingConfiguratorTest { 25 | 26 | private static final String LOG_FILENAME = "selenium.log"; 27 | private static final String[] NO_ARGS = new String[0]; 28 | private List appenders; 29 | private LoggingConfigurator loggingConfigurator; 30 | 31 | @Before 32 | public void setUp() throws Exception { 33 | loggingConfigurator = new LoggingConfigurator(); 34 | appenders = new LinkedList<>(); 35 | Enumeration e = Logger.getRootLogger().getAllAppenders(); 36 | while (e.hasMoreElements()) { 37 | Object appender = e.nextElement(); 38 | if (appender instanceof Appender) { 39 | appenders.add((Appender) appender); 40 | } 41 | } 42 | } 43 | 44 | @Test 45 | public void configure_shouldInstallConsoleAppender() throws Exception { 46 | loggingConfigurator.configure(NO_ARGS); 47 | 48 | Appender appender = Logger.getRootLogger().getAppender("Console"); 49 | assertNotNull(appender); 50 | assertThat(appender, instanceOf(ConsoleAppender.class)); 51 | } 52 | 53 | @Test 54 | public void configure_shouldInstallFileAppenderFromArguments() throws Exception { 55 | 56 | String[] arguments = loggingConfigurator.configure(new String[]{"-log", "target/" + LOG_FILENAME}); 57 | assertEquals(0, arguments.length); 58 | Appender appender = Logger.getRootLogger().getAppender("FileAppender"); 59 | assertNotNull(appender); 60 | assertThat(appender, instanceOf(RollingFileAppender.class)); 61 | assertThat(((RollingFileAppender) appender).getFile(), endsWith(LOG_FILENAME)); 62 | assertThat(arguments.length, is(0)); 63 | } 64 | 65 | @After 66 | public void tearDown() throws Exception { 67 | // restore 68 | Logger.getRootLogger().removeAllAppenders(); 69 | for (Appender appender : appenders) { 70 | Logger.getRootLogger().addAppender(appender); 71 | } 72 | 73 | SLF4JBridgeHandler.uninstall(); 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /selenium-plugins/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | selenium-grid-extensions 5 | io.sterodium 6 | 0.7-SNAPSHOT 7 | ../pom.xml 8 | 9 | 4.0.0 10 | 11 | selenium-plugins 12 | pom 13 | 14 | 15 | 16 | org.seleniumhq.selenium 17 | selenium-server 18 | 19 | 20 | org.slf4j 21 | slf4j-api 22 | 1.7.12 23 | 24 | 25 | org.slf4j 26 | slf4j-simple 27 | 1.7.12 28 | 29 | 30 | 31 | junit 32 | junit 33 | test 34 | 35 | 36 | org.assertj 37 | assertj-core 38 | 3.5.2 39 | test 40 | 41 | 42 | 43 | 44 | protocol 45 | plugin-api 46 | plugin-bridge 47 | plugin-one 48 | 49 | 50 | --------------------------------------------------------------------------------