├── LICENSE ├── README.md ├── app ├── app.js ├── package.json └── public │ ├── angular │ ├── admin.js │ ├── index.html │ ├── login.css │ └── login.html │ └── js │ ├── combined.js │ ├── wowxhr.js │ └── xhook.js ├── assets └── logo.png ├── projects └── selenium-java │ ├── build.gradle │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── sudharsan_selvaraj │ │ │ └── wowxhr │ │ │ ├── DriverStateChangeListener.java │ │ │ ├── HttpMethod.java │ │ │ ├── JsExecutable.java │ │ │ ├── PageLoadManager.java │ │ │ ├── ScriptExecutor.java │ │ │ ├── ScriptProvider.java │ │ │ ├── WowXHR.java │ │ │ ├── WowXHRScriptExecutor.java │ │ │ ├── exceptions │ │ │ └── DriverNotSupportedException.java │ │ │ ├── log │ │ │ ├── DateSerializer.java │ │ │ ├── Log.java │ │ │ ├── RequestLog.java │ │ │ ├── ResponseLog.java │ │ │ └── XHRLog.java │ │ │ └── mock │ │ │ ├── Mock.java │ │ │ ├── MockHttpRequest.java │ │ │ ├── MockHttpResponse.java │ │ │ └── Mockable.java │ └── resources │ │ └── js │ │ ├── scripts.js │ │ └── wowxhr.js │ └── test │ ├── java │ └── io │ │ └── github │ │ └── sudharsan_selvaraj │ │ └── e2e │ │ └── admin_panel │ │ ├── BaseWebDriverTest.java │ │ ├── Color.java │ │ ├── models │ │ ├── Address.java │ │ ├── Post.java │ │ └── User.java │ │ ├── pages │ │ ├── BasePage.java │ │ ├── DashBoard.java │ │ ├── LoginPage.java │ │ └── Table.java │ │ └── tests │ │ ├── NoMockTest.java │ │ ├── RequestMockTests.java │ │ └── ResponseMockTest.java │ └── resources │ ├── responses │ ├── posts_page1.json │ ├── posts_page2.json │ ├── posts_page3.json │ ├── posts_page4.json │ └── users.json │ └── suites │ └── testng.xml └── wow-xhr-js ├── .npmignore ├── package.json ├── src ├── XhookListener.ts ├── index.ts ├── modules │ ├── LogInfo.ts │ ├── XhrLog.ts │ ├── XhrMock.ts │ └── xhook.js └── types │ └── index.ts ├── tsconfig.json └── webpack.config.js /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | Simple library to manipulate HTTP requests and responses, capture the network logs made by the browser using selenium tests without using any proxies 7 |

8 | 9 | Although selenium is used for e2e and especially UI testing, you might want to mock certain HTTP response to test few 10 | error scenarios and to assess HTTP requests done by your client code (e.g. when you don't have immediate UI feedback, 11 | like in metrics or tracking calls). 12 | 13 | There's one catch though: you can't intercept HTTP calls that are initiated on page load (like in most SPAs), as it 14 | requires some setup work that can only be done after the page is loaded (due to limitations in selenium). That means you 15 | can just capture requests that were initiated inside a test. If you're fine with that, this plugin might be for you, so 16 | read on. 17 | 18 | ## Installation 19 | 20 | ### Maven 21 | 22 | ```xml 23 | 24 | 25 | io.github.sudharsan-selvaraj 26 | wow-xhr 27 | 1.0.0 28 | 29 | ``` 30 | 31 | ### Gradle 32 | 33 | ```groovy 34 | implementation group: 'io.github.sudharsan-selvaraj', name: 'wow-xhr', version: '1.0.0' 35 | ``` 36 | 37 | Also while downloading selenium, make sure to exclude `net.bytebuddy:byte-buddy` library by using 38 | 39 | ### Maven 40 | ```xml 41 | 42 | org.seleniumhq.selenium 43 | selenium-java 44 | 3.141.59 45 | 46 | 47 | net.bytebuddy 48 | byte-buddy 49 | 50 | 51 | 52 | ``` 53 | 54 | ### Gradle 55 | ```groovy 56 | implementation (group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.141.59') { 57 | exclude group: 'net.bytebuddy', module: 'byte-buddy' 58 | } 59 | ``` 60 | 61 | ## Features: 62 | 63 | 1. Intercept any HTTP request and modify the header, query param, body of the request. 64 | 2. Modify the header, body, status of the HTTP response. 65 | 3. Get the complete details of all API calls made by the browser along with the header, body, initiated time and 66 | completed time. 67 | 4. Add a delay to any API call to simulate network speed for specific API call. 68 | 69 | ## Quickstart 70 | 71 | Create wowXhr object, 72 | 73 | ```java 74 | WowXHR wowXhr = new WowXHR(new ChromeDriver()); //any selenium webdriver or RemoteWebDriver 75 | WebDriver driver = wowXhr.getMockDriver() 76 | ``` 77 | 78 | That's it. Now the driver object can be used in the test. 79 | No extra configurations required for browser running on remote machine or browserstack or saucelabs. 80 | 81 | ## Mock 82 | 83 | ### Modify HTTP request 84 | 85 | #### Add or update header 86 | ```java 87 | import static io.github.sudharsan_selvaraj.wowxhr.mock.Mockable.*; 88 | 89 | driver.get("http://localhost:3000"); 90 | 91 | wowXHR.mock().add( 92 | whenGET("/api") 93 | .modifyRequest( 94 | mockRequest() 95 | .setHeader("Authorization", "invalid-auth-token") 96 | ) 97 | ); 98 | ``` 99 | 100 | Above mock will update the `Authorization` header with `invalid-auth-token` for all HTTP request that has `/api` regular expression in its url. 101 | 102 | #### Add or update query param 103 | ```java 104 | import static io.github.sudharsan_selvaraj.wowxhr.mock.Mockable.*; 105 | 106 | driver.get("http://localhost:3000"); 107 | 108 | wowXHR.mock().add( 109 | whenGET("/api/users") 110 | .modifyRequest( 111 | mockRequest() 112 | .setQueryParam("filter_fname", "sudharsan") 113 | ) 114 | ); 115 | ``` 116 | Above mock will add `?filter_fname=sudharsan` or update the value of `filter_fname` query param for all `/api/users` api requests. 117 | 118 | #### Update the request body 119 | ```java 120 | import static io.github.sudharsan_selvaraj.wowxhr.mock.Mockable.*; 121 | 122 | driver.get("http://localhost:3000"); 123 | 124 | wowXHR.mock().add( 125 | whenPOST("/api/login") 126 | .modifyRequest( 127 | mockRequest() 128 | .setBody("{\"email\":\"invalidemail@emial.com\",\"password\":\"somepassword\"}") 129 | ) 130 | ); 131 | ``` 132 | Above mock will update the request body with `{\"email\":\"invalidemail@emial.com\",\"password\":\"somepassword\"}` for all subsequent `/api/login` requests. 133 | 134 | 135 | ### Modify HTTP response 136 | 137 | #### Add or update header 138 | ```java 139 | import static io.github.sudharsan_selvaraj.wowxhr.mock.Mockable.*; 140 | 141 | driver.get("http://localhost:3000"); 142 | 143 | wowXHR.mock().add( 144 | whenPOST("/api/login") 145 | .respond( 146 | mockResponse() 147 | .withHeader("Set-Cookie", "refresh-token=invalid-refresh-token; expires=Mon, 17-Jul-2021 16:06:00 GMT; Max-Age=31449600; Path=/; secure") 148 | ) 149 | ); 150 | ``` 151 | Above mock will update the response header and add's the new cookie to the browser. 152 | 153 | #### Update the response body 154 | ```java 155 | import static io.github.sudharsan_selvaraj.wowxhr.mock.Mockable.*; 156 | 157 | driver.get("http://localhost:3000"); 158 | 159 | wowXHR.mock().add( 160 | whenPOST("/api/login") 161 | .respond( 162 | mockResponse() 163 | .withBody("{\"error\": \"true\" ,\"message\" : \"User account is locked\"}") 164 | ) 165 | ); 166 | ``` 167 | Above mock will update the response body with `"{\"error\": \"true\" ,\"message\" : \"User account is locked\"}"` for all login api calls. 168 | 169 | #### update the response status 170 | ```java 171 | import static io.github.sudharsan_selvaraj.wowxhr.mock.Mockable.*; 172 | 173 | driver.get("http://localhost:3000"); 174 | 175 | wowXHR.mock().add( 176 | whenPOST("/api/login") 177 | .respond( 178 | mockResponse() 179 | .withStatus(500) //internal server error 180 | ) 181 | ); 182 | ``` 183 | Above mock will change the response status code to `500` and makes the application to assume that the API call has been failed. 184 | 185 | #### update the response status 186 | ```java 187 | import static io.github.sudharsan_selvaraj.wowxhr.mock.Mockable.*; 188 | 189 | driver.get("http://localhost:3000"); 190 | 191 | wowXHR.mock().add( 192 | whenPOST("/api/login") 193 | .respond( 194 | mockResponse() 195 | .withDelay(10) //in seconds 196 | ) 197 | ); 198 | ``` 199 | Above mock will add a delay of 10 seconds before the response is returned to the application. 200 | This will be very useful in testing any loading UI elements that is displayed when API calls are made. 201 | 202 | ### Pausing and Resume the mock 203 | 204 | At any point in the test if you decide to pause the mocking, then it can be achieved using 205 | ```java 206 | wowhXhr.mock().pause(); 207 | ``` 208 | 209 | and you can resume using, 210 | 211 | ```java 212 | wowhXhr.mock().resume(); 213 | ``` 214 | 215 | ## Logs 216 | 217 | ### Get XHR logs 218 | 219 | ```java 220 | List logs = wowXHR.log().getXHRLogs(); 221 | logs.forEach(log -> { 222 | Date initiatedTime = log.getInitiatedTime(); 223 | Date completedTime = log.getCompletedTime(); 224 | 225 | String method = log.getRequest().getMethod().name(); // GET or POST or PUT etc 226 | String url = log.getRequest().getUrl(); 227 | Integer status = log.getResponse().getStatus(); 228 | String requestBody = (String) log.getRequest().getBody(); 229 | String responseBody = (String) log.getResponse().getBody(); 230 | Map requestHeaders = log.getRequest().getHeaders(); 231 | Map responseHeaders = log.getResponse().getHeaders(); 232 | }); 233 | ``` 234 | 235 | Note: By default `wowXHR.log().getXHRLogs()` will clear the previously fetched logs. If you want the logs to be preserved, then you can use the overloaded method 236 | `wowXHR.log().getXHRLogs(false)` 237 | 238 | ### Manually clear the logs 239 | 240 | ```java 241 | wowXHR.log().clear(); 242 | ``` 243 | 244 | ## Upcoming features 245 | 246 | 1. Support for making the tests to wait for all api calls to get completed (similar to 247 | protractor's `waitForAngular`) 248 | 2. Ability to mock the HTTP requests that are triggered during initial page load. 249 | 3. Currently, the request can be mocked based on its URL only(Regex). In the future, we will also support for finding requests using query params(partial and exact) and body(partial and exact). 250 | 4. TBD -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | var path = require("path"); 2 | var proxy = require('express-http-proxy'); 3 | var express = require('express'); 4 | const queryString = require('query-string'); 5 | var app = express(); 6 | 7 | var authorize = function (req, res, next) { 8 | if(req.headers['wow-xhr-token'] && req.headers["wow-xhr-token"] === "invalid") { 9 | console.log("Proxy found") 10 | return res.status(500).send({}); 11 | } else { 12 | console.log("No proxy found") 13 | return next(); 14 | } 15 | } 16 | 17 | app.post("/api/login", proxy('https://reqres.in/api/login')); 18 | app.use('/node_modules', express.static(path.join(__dirname, '/node_modules/'))) 19 | app.use('/js', express.static(path.join(__dirname, '/public/js/'))) 20 | app.use('/angular', express.static(path.join(__dirname, '/public/angular'))) 21 | app.use("/api/delay", [authorize] ,proxy('http://jsonplaceholder.typicode.com', { 22 | proxyReqPathResolver: function (req) { 23 | return new Promise(function (resolve, reject) { 24 | var params = queryString.parse(req.url.split("?")[1] || "?a=a"); 25 | if(params["delay"]) { 26 | console.log(params["delay"] +" ms delay") 27 | setTimeout(function () { 28 | resolve(req.url); 29 | }, params["delay"]); 30 | } else { 31 | resolve(req.url) 32 | } 33 | }); 34 | } 35 | })); 36 | app.use("/api", proxy('http://jsonplaceholder.typicode.com')); 37 | 38 | app.get("/ng", function (req,res) { 39 | res.sendFile(path.join(__dirname, '/public/angular/index.html')); 40 | }) 41 | 42 | app.get("/ng/login", function (req,res) { 43 | res.sendFile(path.join(__dirname, '/public/angular/login.html')); 44 | }) 45 | 46 | app.listen(3000); -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "express": "^4.17.1", 13 | "express-http-proxy": "^1.6.2", 14 | "jquery": "^3.6.0", 15 | "ng-admin": "^1.0.13", 16 | "query-string": "^7.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/public/angular/admin.js: -------------------------------------------------------------------------------- 1 | var myApp = angular.module('myApp', ['ng-admin']); 2 | 3 | myApp.config(['NgAdminConfigurationProvider', function (nga) { 4 | var admin = nga.application('My First Admin') 5 | .baseApiUrl(location.origin+'/api/delay/'); // main API endpoint 6 | 7 | var comment = nga.entity('comments'); // the API endpoint for users will be 'http://jsonplaceholder.typicode.com/comments/:id 8 | admin.addEntity(comment); 9 | 10 | var user = nga.entity('users'); // the API endpoint for users will be 'http://jsonplaceholder.typicode.com/users/:id 11 | user.listView() 12 | .fields([ 13 | nga.field('name').isDetailLink(true), 14 | nga.field('username'), 15 | nga.field('email') 16 | ]); 17 | user.creationView().fields([ 18 | nga.field('name') 19 | .validation({ required: true, minlength: 3, maxlength: 100 }), 20 | nga.field('username') 21 | .attributes({ placeholder: 'No space allowed, 5 chars min' }) 22 | .validation({ required: true, pattern: '[A-Za-z0-9\.\-_]{5,20}' }), 23 | nga.field('email', 'email') 24 | .validation({ required: true }), 25 | nga.field('address.street') 26 | .label('Street'), 27 | nga.field('address.city') 28 | .label('City'), 29 | nga.field('address.zipcode') 30 | .label('Zipcode') 31 | .validation({ pattern: '[A-Z\-0-9]{5,10}' }), 32 | nga.field('phone'), 33 | nga.field('website') 34 | .validation({ validator: function(value) { 35 | if (value.indexOf('http://') !== 0) throw new Error ('Invalid url in website'); 36 | } }) 37 | ]); 38 | user.editionView().fields(user.creationView().fields()); 39 | admin.addEntity(user) 40 | 41 | var post = nga.entity('posts'); // the API endpoint for users will be 'http://jsonplaceholder.typicode.com/users/:id 42 | post.readOnly(); 43 | post.listView() 44 | .fields([ 45 | nga.field('title').isDetailLink(true), 46 | nga.field('body', 'text') 47 | .map(function truncate(value) { 48 | if (!value) return ''; 49 | return value.length > 50 ? value.substr(0, 50) + '...' : value; 50 | }), 51 | nga.field('userId', 'reference') 52 | .targetEntity(user) 53 | .targetField(nga.field('username')) 54 | .label('Author') 55 | ]) 56 | .listActions(['show']) 57 | .batchActions([]) 58 | .filters([ 59 | nga.field('q') 60 | .label('') 61 | .pinned(true) 62 | .template('

'), 63 | nga.field('userId', 'reference') 64 | .targetEntity(user) 65 | .targetField(nga.field('username')) 66 | .label('User') 67 | ]); 68 | post.showView().fields([ 69 | nga.field('title'), 70 | nga.field('body', 'text'), 71 | nga.field('userId', 'reference') 72 | .targetEntity(user) 73 | .targetField(nga.field('username')) 74 | .label('User'), 75 | nga.field('comments', 'referenced_list') 76 | .targetEntity(nga.entity('comments')) 77 | .targetReferenceField('postId') 78 | .targetFields([ 79 | nga.field('email'), 80 | nga.field('name') 81 | ]) 82 | .sortField('id') 83 | .sortDir('DESC'), 84 | ]); 85 | admin.addEntity(post); 86 | 87 | admin.menu(nga.menu() 88 | .addChild(nga.menu(user).icon('')) 89 | .addChild(nga.menu(post).icon('')) 90 | ); 91 | 92 | nga.configure(admin); 93 | }]); 94 | 95 | myApp.config(['RestangularProvider', function (RestangularProvider) { 96 | RestangularProvider.addFullRequestInterceptor(function(element, operation, what, url, headers, params) { 97 | console.log("API called "+ url) 98 | if (operation == "getList") { 99 | // custom pagination params 100 | if (params._page) { 101 | params._start = (params._page - 1) * params._perPage; 102 | params._end = params._page * params._perPage; 103 | } 104 | delete params._page; 105 | delete params._perPage; 106 | // custom sort params 107 | if (params._sortField) { 108 | params._sort = params._sortField; 109 | params._order = params._sortDir; 110 | delete params._sortField; 111 | delete params._sortDir; 112 | } 113 | // custom filters 114 | if (params._filters) { 115 | for (var filter in params._filters) { 116 | params[filter] = params._filters[filter]; 117 | } 118 | delete params._filters; 119 | } 120 | } 121 | return { params: params }; 122 | }); 123 | }]); -------------------------------------------------------------------------------- /app/public/angular/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My First Admin 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/public/angular/login.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | Posters Galore Login 11 | 12 | 13 | 14 | 15 | 16 | 21 | 48 | 49 | 50 |
51 |
52 |

Login

53 |
54 | 55 |
56 |
57 | 58 | 59 |
60 | 61 |
62 |

Invalid username/password

63 |

loading..

64 | 65 |
66 |
67 |
68 | 69 | -------------------------------------------------------------------------------- /app/public/js/combined.js: -------------------------------------------------------------------------------- 1 | (()=>{var e={788:function(){(function(e){var t,n,r,o,s,a,i,u,c,f,l,d,p,h,y,g,v,m,w,b,x,S,E,R,O,T,k,_,C=[].indexOf||function(e){for(var t=0,n=this.length;t=0||(s=s===e?r[t].length:s,r[t].splice(s,0,n))},n[c]=function(t,n){var s;t!==e?(n===e&&(r[t]=[]),-1!==(s=o(t).indexOf(n))&&o(t).splice(s,1)):r={}},n[s]=function(){var e,r,s,a,i,u,c;for(r=(e=O(arguments)).shift(),t||(e[0]=x(e[0],b(r))),(a=n["on"+r])&&a.apply(n,e),s=i=0,u=(c=o(r).concat(o("*"))).length;i2)throw"invalid hook";return k[f](n,e,t)},k.after=function(e,n){if(e.length<2||e.length>3)throw"invalid hook";return k[f](t,e,n)},k.enable=function(){d[g]=y,"function"==typeof p&&(d.fetch=p),i&&(d.FormData=h)},k.disable=function(){d[g]=k[g],d.fetch=k.fetch,i&&(d.FormData=i)},v=k.headers=function(e,t){var n,r,o,s,a,i,u,c,f;switch(null==t&&(t={}),typeof e){case"object":for(o in r=[],e)a=e[o],s=o.toLowerCase(),r.push(s+":\t"+a);return r.join("\n")+"\n";case"string":for(u=0,c=(r=e.split("\n")).length;ue&&e<4;)c.readyState=++e,1===e&&c[s]("loadstart",{}),2===e&&N(),4===e&&(N(),L()),c[s]("readystatechange",{}),4===e&&(!1===w.async?a():setTimeout(a,0))},a=function(){d||c[s]("load",{}),c[s]("loadend",{}),d&&(c.readyState=0)},e=0,O=function(e){var n,r;4===e?(n=k.listeners(t),(r=function(){var e;return n.length?2===(e=n.shift()).length?(e(w,b),r()):3===e.length&&w.async?e(w,b,r):r():i(4)})()):i(e)},c=(w={}).xhr=o(),I.onreadystatechange=function(e){try{2===I.readyState&&m()}catch(e){}4===I.readyState&&(_=!1,m(),y()),O(I.readyState)},p=function(){d=!0},c[f]("error",p),c[f]("timeout",p),c[f]("abort",p),c[f]("progress",(function(){e<3?O(3):c[s]("readystatechange",{})})),("withCredentials"in I||k.addWithCredentials)&&(c.withCredentials=!1),c.status=0;for(P=0,D=(H=r.concat(l)).length;P{"use strict";var e,t=function(){},r="_xhr_log_pending_calls_",o="_xhr_log_completed_calls_",s=window,a=function(){function e(){[r,o].forEach((function(e){s.sessionStorage.getItem(e)||s.sessionStorage.setItem(e,"[]")}))}return e.prototype.beforeXHR=function(e){var n=e.wow_xhr_id,o=new t;o.id=n,o.request=e,o.initiatedTime=Date.now();var a=JSON.parse(s.sessionStorage.getItem(r));return a.push(o),s.sessionStorage.setItem(r,JSON.stringify(a)),Promise.resolve()},e.prototype.afterXHR=function(e,t){var n=e.wow_xhr_id,a=JSON.parse(s.sessionStorage.getItem(r)),i=a.findIndex((function(e){return e.id==n}));if(i){var u=a[i];u.response=t,u.completedTime=Date.now(),a.splice(i,1);var c=JSON.parse(s.sessionStorage.getItem(o));return c.push(u),this.setCompletedCalls(c),this.setPendingCalls(a),Promise.resolve(void 0)}},e.prototype.setCompletedCalls=function(e){s.sessionStorage.setItem(o,JSON.stringify(e))},e.prototype.setPendingCalls=function(e){s.sessionStorage.setItem(r,JSON.stringify(e))},e}(),i="_xhr_mock_",u="_xhr_mock_paused_",c=window,f=function(){function e(){c.sessionStorage.getItem(i)||c.sessionStorage.setItem(i,"[]"),c.sessionStorage.getItem(u)||c.sessionStorage.setItem(u,"false")}return e.prototype.findMock=function(e){var t=JSON.parse(c.sessionStorage.getItem(i)||"[]");if("true"!=c.sessionStorage.getItem(u))return t.map((function(e){return JSON.parse(e)})).filter((function(t){return t.method===e.method&&new RegExp(t.url).test(e.url)}))},e.prototype.beforeXHR=function(e){var t=this;return new Promise((function(n,r){var o=e.method,s=e.url;t.findMock({method:o,url:s}).forEach((function(t){if(t&&t.mockRequest){var n=t.mockRequest,r=n.headers,s=n.body,a=n.queryParams;if(r&&Object.assign(e.headers,r),s&&new RegExp(o).test("POST|PUT|PATCH")&&(e.body=s),a&&Object.keys(a).length){var i=new URL(e.url);i.searchParams.forEach((function(e,t){a.hasOwnProperty(t)&&(i.searchParams.set(t,a[t]),delete a[t])})),Object.keys(a).forEach((function(e){i.searchParams.append(e,a[e])})),e.url=i.toString(),console.log(e)}}})),n()}))},e.prototype.afterXHR=function(e,t){var n=this;return new Promise((function(r,o){var s=e.method,a=e.url,i=n.findMock({method:s,url:a}),u=0;console.log("After xhr:"),console.log(i),i.forEach((function(e){if(e&&e.mockResponse){var n=e.mockResponse,r=n.headers,o=n.body,s=n.status,a=n.delay;r&&Object.assign(t.headers,r),o&&(t.text=t.data=o),s&&(t.status=s),a&&(u=1e3*a)}})),setTimeout(r,u)}))},e}(),l=new Uint8Array(16);function d(){if(!e&&!(e="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return e(l)}const p=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i,h=function(e){return"string"==typeof e&&p.test(e)};for(var y=[],g=0;g<256;++g)y.push((g+256).toString(16).substr(1));const v=function(e,t,n){var r=(e=e||{}).random||(e.rng||d)();if(r[6]=15&r[6]|64,r[8]=63&r[8]|128,t){n=n||0;for(var o=0;o<16;++o)t[n+o]=r[o];return t}return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=(y[e[t+0]]+y[e[t+1]]+y[e[t+2]]+y[e[t+3]]+"-"+y[e[t+4]]+y[e[t+5]]+"-"+y[e[t+6]]+y[e[t+7]]+"-"+y[e[t+8]]+y[e[t+9]]+"-"+y[e[t+10]]+y[e[t+11]]+y[e[t+12]]+y[e[t+13]]+y[e[t+14]]+y[e[t+15]]).toLowerCase();if(!h(n))throw TypeError("Stringified UUID is invalid");return n}(r)};var m=function(e,t,n,r){return new(n||(n=Promise))((function(o,s){function a(e){try{u(r.next(e))}catch(e){s(e)}}function i(e){try{u(r.throw(e))}catch(e){s(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,i)}u((r=r.apply(e,t||[])).next())}))},w=function(e,t){var n,r,o,s,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:i(0),throw:i(1),return:i(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function i(s){return function(i){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return a.label++,{value:s[1],done:!1};case 5:a.label++,r=s[1],s=[0];continue;case 7:s=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1]{"use strict";var e,t=function(){function e(){}return Object.defineProperty(e.prototype,"id",{get:function(){return this._id},set:function(e){this._id=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"initiatedTime",{get:function(){return this._initiatedTime},set:function(e){this._initiatedTime=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"completedTime",{get:function(){return this._completedTime},set:function(e){this._completedTime=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"request",{get:function(){return this._request},set:function(e){this._request=e},enumerable:!1,configurable:!0}),Object.defineProperty(e.prototype,"response",{get:function(){return this._response},set:function(e){this._response=e},enumerable:!1,configurable:!0}),e}(),n=function(){function e(){this._pendingXhrCalls=new Map,this._completedXhrCalls=[]}return e.prototype.beforeXHR=function(e){var n=e.wow_xhr_id,r=new t;return r.id=n,r.request=e,r.initiatedTime=Date.now(),this._pendingXhrCalls.set(n,r),Promise.resolve()},e.prototype.afterXHR=function(e,t){var n=e.wow_xhr_id,r=this._pendingXhrCalls.get(n);if(r)return r.response=t,r.completedTime=Date.now(),this._completedXhrCalls.push(r),this._pendingXhrCalls.delete(n),Promise.resolve(void 0)},e.prototype.getCompletedLogs=function(){return this._completedXhrCalls},e.prototype.flushLogs=function(){return this._completedXhrCalls=[]},e.prototype.getPendingXhrCalls=function(){return Array.from(this._pendingXhrCalls.values())},e}(),r=function(){function e(){this._mocks=[],this._isPaused=!1}return e.prototype.findMock=function(e){if(!this._isPaused)return this._mocks.filter((function(t){return t.method===e.method&&new RegExp(t.url).test(e.url)}))},e.prototype.registerMock=function(e){this._mocks.push(e)},e.prototype.pause=function(){this._isPaused=!0},e.prototype.resume=function(){this._isPaused=!1},e.prototype.beforeXHR=function(e){var t=this;return new Promise((function(n,r){var o=e.method,i=e.url;t.findMock({method:o,url:i}).forEach((function(t){if(t&&t.mockRequest){var n=t.mockRequest,r=n.headers,i=n.body,u=n.queryParams;if(r&&Object.assign(e.headers,r),i&&new RegExp(o).test("POST|PUT|PATCH")&&(e.body=i),u&&Object.keys(u).length){var s=new URL(e.url);s.searchParams.forEach((function(e,t){u.hasOwnProperty(t)&&(s.searchParams.set(t,u[t]),delete u[t])})),Object.keys(u).forEach((function(e){s.searchParams.append(e,u[e])})),e.url=s.toString(),console.log(e)}}})),n()}))},e.prototype.afterXHR=function(e,t){var n=this;return new Promise((function(r,o){var i=e.method,u=e.url,s=n.findMock({method:i,url:u}),a=0;s.forEach((function(e){if(e&&e.mockResponse){var n=e.mockResponse,r=n.headers,o=n.body,i=n.status,u=n.delay;r&&Object.assign(t.headers,r),o&&(t.text=t.data=o),i&&(t.status=i),u&&(a=1e3*u)}})),setTimeout(r,a)}))},e}(),o=new Uint8Array(16);function i(){if(!e&&!(e="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return e(o)}const u=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i,s=function(e){return"string"==typeof e&&u.test(e)};for(var a=[],c=0;c<256;++c)a.push((c+256).toString(16).substr(1));const l=function(e,t,n){var r=(e=e||{}).random||(e.rng||i)();if(r[6]=15&r[6]|64,r[8]=63&r[8]|128,t){n=n||0;for(var o=0;o<16;++o)t[n+o]=r[o];return t}return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=(a[e[t+0]]+a[e[t+1]]+a[e[t+2]]+a[e[t+3]]+"-"+a[e[t+4]]+a[e[t+5]]+"-"+a[e[t+6]]+a[e[t+7]]+"-"+a[e[t+8]]+a[e[t+9]]+"-"+a[e[t+10]]+a[e[t+11]]+a[e[t+12]]+a[e[t+13]]+a[e[t+14]]+a[e[t+15]]).toLowerCase();if(!s(n))throw TypeError("Stringified UUID is invalid");return n}(r)};var f=function(e,t,n,r){return new(n||(n=Promise))((function(o,i){function u(e){try{a(r.next(e))}catch(e){i(e)}}function s(e){try{a(r.throw(e))}catch(e){i(e)}}function a(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(u,s)}a((r=r.apply(e,t||[])).next())}))},h=function(e,t){var n,r,o,i,u={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:s(0),throw:s(1),return:s(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function s(i){return function(s){return function(i){if(n)throw new TypeError("Generator is already executing.");for(;u;)try{if(n=1,r&&(o=2&i[0]?r.return:i[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,i[1])).done)return o;switch(r=0,o&&(i=[2&i[0],o.value]),i[0]){case 0:case 1:o=i;break;case 4:return u.label++,{value:i[1],done:!1};case 5:u.label++,r=i[1],i=[0];continue;case 7:i=u.ops.pop(),u.trys.pop();continue;default:if(!((o=(o=u.trys).length>0&&o[o.length-1])||6!==i[0]&&2!==i[0])){u=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]0&&o[o.length-1])||6!==i[0]&&2!==i[0])){u=0;continue}if(3===i[0]&&(!o||i[1]>o[0]&&i[1]=0||(r=r===e?o[t].length:r,o[t].splice(r,0,n))},n[i]=function(t,n){var r;t!==e?(n===e&&(o[t]=[]),-1!==(r=a(t).indexOf(n))&&a(t).splice(r,1)):o={}},n[r]=function(){var e,r,o,s,i,u,f;for(r=(e=L(arguments)).shift(),t||(e[0]=b(e[0],m(r))),(s=n["on"+r])&&s.apply(n,e),o=i=0,u=(f=a(r).concat(a("*"))).length;i2)throw"invalid hook";return R[u]("before",e,t)},R.after=function(e,t){if(e.length<2||e.length>3)throw"invalid hook";return R[u]("after",e,t)},R.enable=function(){l[h]=p,"function"==typeof c&&(l.fetch=c),a&&(l.FormData=d)},R.disable=function(){l[h]=R[h],l.fetch=R.fetch,a&&(l.FormData=a)},y=R.headers=function(e,t){var n,r,o,a,s,i,u,f,l;switch(null==t&&(t={}),typeof e){case"object":for(o in r=[],e)s=e[o],a=o.toLowerCase(),r.push(a+":\t"+s);return r.join("\n")+"\n";case"string":for(u=0,f=(r=e.split("\n")).length;ue&&e<4;)i.readyState=++e,1===e&&i[r]("loadstart",{}),2===e&&O(),4===e&&(O(),C()),i[r]("readystatechange",{}),4===e&&(!1===g.async?o():setTimeout(o,0))},o=function(){l||i[r]("load",{}),i[r]("loadend",{}),l&&(i.readyState=0)},e=0,L=function(e){var t,n;4===e?(t=R.listeners("after"),(n=function(){var e;return t.length?2===(e=t.shift()).length?(e(g,m),n()):3===e.length&&g.async?e(g,m,n):n():a(4)})()):a(e)},i=(g={}).xhr=n(),S.onreadystatechange=function(e){try{2===S.readyState&&v()}catch(e){}4===S.readyState&&(D=!1,v(),p()),L(S.readyState)},c=function(){l=!0},i[u]("error",c),i[u]("timeout",c),i[u]("abort",c),i[u]("progress",function(){e<3?L(3):i[r]("readystatechange",{})}),("withCredentials"in S||R.addWithCredentials)&&(i.withCredentials=!1),i.status=0;for(A=0,H=(M=t.concat(f)).length;A \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /projects/selenium-java/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /projects/selenium-java/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'selenium-java' 2 | 3 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/DriverStateChangeListener.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr; 2 | 3 | public interface DriverStateChangeListener { 4 | 5 | public void onStateChanged(Boolean isScriptInjected); 6 | 7 | } 8 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/HttpMethod.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr; 2 | 3 | public enum HttpMethod { 4 | GET, 5 | POST, 6 | PUT, 7 | PATCH, 8 | DELETE 9 | } 10 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/JsExecutable.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr; 2 | 3 | import lombok.AllArgsConstructor; 4 | 5 | @AllArgsConstructor 6 | public class JsExecutable { 7 | 8 | private ScriptExecutor executor; 9 | 10 | protected Object executeScript(String string, Object... args) { 11 | return executor.executeScript(string, args); 12 | } 13 | 14 | protected Object executeAsyncScript(String string, Object... args) { 15 | return executor.executeAsyncScript(string, args); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/PageLoadManager.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr; 2 | 3 | import org.checkerframework.checker.nullness.compatqual.NullableDecl; 4 | import org.openqa.selenium.JavascriptExecutor; 5 | import org.openqa.selenium.PageLoadStrategy; 6 | import org.openqa.selenium.WebDriver; 7 | import org.openqa.selenium.remote.RemoteWebDriver; 8 | import org.openqa.selenium.support.ui.ExpectedCondition; 9 | import org.openqa.selenium.support.ui.FluentWait; 10 | 11 | import java.time.Duration; 12 | 13 | public class PageLoadManager implements DriverStateChangeListener { 14 | 15 | private final PageLoadStrategy strategy; 16 | private final RemoteWebDriver webDriver; 17 | private final ScriptExecutor scriptExecutor; 18 | 19 | public PageLoadManager(RemoteWebDriver webDriver, ScriptExecutor executor) { 20 | this.webDriver = webDriver; 21 | strategy = getPageLoadStrategy(webDriver); 22 | this.scriptExecutor = executor; 23 | executor.addStateChangeListener(this); 24 | } 25 | 26 | @Override 27 | public void onStateChanged(Boolean isScriptInjected) { 28 | try { 29 | if (!strategy.equals(PageLoadStrategy.NORMAL)) { 30 | FluentWait wait = new FluentWait(webDriver) 31 | .pollingEvery(Duration.ofMillis(100)) 32 | .withTimeout(Duration.ofSeconds(60)); 33 | wait.until(new ExpectedCondition() { 34 | @NullableDecl 35 | @Override 36 | public Boolean apply(@NullableDecl WebDriver input) { 37 | return (Boolean) scriptExecutor.executeScript(ScriptProvider.getFunction("waitForPageLoad")); 38 | } 39 | }); 40 | } 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | } 44 | } 45 | 46 | private PageLoadStrategy getPageLoadStrategy(RemoteWebDriver driver) { 47 | Object strategy = driver.getCapabilities().getCapability("pageLoadStrategy"); 48 | if (strategy != null) { 49 | return PageLoadStrategy.valueOf(strategy.toString().toUpperCase()); 50 | } else { 51 | return PageLoadStrategy.NORMAL; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/ScriptExecutor.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr; 2 | 3 | public interface ScriptExecutor { 4 | 5 | public Object executeScript(String script, Object... args); 6 | 7 | public Object executeAsyncScript(String script, Object... args); 8 | 9 | public void addStateChangeListener(DriverStateChangeListener listener); 10 | } 11 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/ScriptProvider.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Objects; 6 | import java.util.Scanner; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | public class ScriptProvider { 11 | protected static String wowXhrScript, utilFunctions; 12 | protected static final Map functions = new HashMap<>(); 13 | 14 | static { 15 | wowXhrScript = readScriptFromResource("/js/wowxhr.js"); 16 | utilFunctions = readScriptFromResource("/js/scripts.js"); 17 | iterateOverJsFunctionsInSource(utilFunctions); 18 | functions.put("wowxhr", wowXhrScript); 19 | } 20 | 21 | public static String getFunction(String functionName) { 22 | return functions.get(functionName); 23 | } 24 | 25 | private static String readScriptFromResource(String fileName) { 26 | return new Scanner(Objects.requireNonNull(ScriptProvider.class.getResourceAsStream(fileName)), "UTF-8").useDelimiter("\\A").next(); 27 | } 28 | 29 | private static void iterateOverJsFunctionsInSource(String src) { 30 | Pattern ps = Pattern.compile("^function.* \\{$", Pattern.MULTILINE); 31 | Pattern pe = Pattern.compile("^\\}", Pattern.MULTILINE); 32 | Matcher m = ps.matcher(src); 33 | boolean more = true; 34 | while (more && m.find()) { 35 | src = src.substring(m.start()); 36 | Matcher m2 = pe.matcher(src); 37 | if (m2.find()) { 38 | String body = src.substring(0, m2.start()); 39 | storeJavaScriptFunction(body); 40 | src = src.substring(body.length()); 41 | m = ps.matcher(src); 42 | } else { 43 | more = false; 44 | } 45 | } 46 | } 47 | 48 | private static void storeJavaScriptFunction(String body) { 49 | Pattern regFn = Pattern.compile("^function ([a-zA-Z0-9]+)\\(", Pattern.MULTILINE); 50 | Matcher m = regFn.matcher(body); 51 | String fnName; 52 | if (m.find()) { 53 | fnName = m.group(1); 54 | } else { 55 | Pattern fnPro = Pattern.compile("^functions\\.([a-zA-Z0-9]+) ", Pattern.MULTILINE); 56 | Matcher m2 = fnPro.matcher(body); 57 | if (m2.find()) { 58 | fnName = m2.group(1); 59 | } else { 60 | return; 61 | } 62 | } 63 | functions.put(fnName, body.substring(body.indexOf("{") + 1)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/WowXHR.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr; 2 | 3 | import io.github.sudharsan_selvaraj.SpyDriver; 4 | import io.github.sudharsan_selvaraj.wowxhr.exceptions.DriverNotSupportedException; 5 | import io.github.sudharsan_selvaraj.wowxhr.log.Log; 6 | import io.github.sudharsan_selvaraj.wowxhr.mock.Mock; 7 | import org.openqa.selenium.JavascriptExecutor; 8 | import org.openqa.selenium.WebDriver; 9 | import org.openqa.selenium.remote.RemoteWebDriver; 10 | 11 | public class WowXHR { 12 | 13 | private T originalDriver; 14 | private SpyDriver spyDriver; 15 | private final Mock mock; 16 | private final Log log; 17 | private final PageLoadManager pageLoadManager; 18 | 19 | public WowXHR(T driver) throws DriverNotSupportedException { 20 | this(new SpyDriver(driver)); 21 | } 22 | 23 | public WowXHR(SpyDriver spyDriver) throws DriverNotSupportedException { 24 | initializeDriver(spyDriver.getWrappedDriver()); 25 | this.spyDriver = spyDriver; 26 | WowXHRScriptExecutor scriptExecutor = new WowXHRScriptExecutor<>((JavascriptExecutor) originalDriver); 27 | mock = new Mock(scriptExecutor); 28 | log = new Log(scriptExecutor); 29 | pageLoadManager = new PageLoadManager((RemoteWebDriver) spyDriver.getWrappedDriver(), scriptExecutor); 30 | spyDriver.addListener(scriptExecutor); 31 | } 32 | 33 | private void initializeDriver(T driver) throws DriverNotSupportedException { 34 | if (!(driver instanceof JavascriptExecutor)) { 35 | throw new DriverNotSupportedException(driver.getClass().getName()); 36 | } 37 | originalDriver = driver; 38 | } 39 | 40 | public T getMockDriver() { 41 | return spyDriver.getSpyDriver(); 42 | } 43 | 44 | public Mock mock() { 45 | return mock; 46 | } 47 | 48 | public Log log() { 49 | return log; 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/WowXHRScriptExecutor.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr; 2 | 3 | import io.github.sudharsan_selvaraj.SpyDriverListener; 4 | import io.github.sudharsan_selvaraj.types.driver.DriverCommand; 5 | import io.github.sudharsan_selvaraj.types.driver.DriverCommandException; 6 | import io.github.sudharsan_selvaraj.types.driver.DriverCommandResult; 7 | import io.github.sudharsan_selvaraj.types.element.ElementCommand; 8 | import io.github.sudharsan_selvaraj.types.element.ElementCommandException; 9 | import io.github.sudharsan_selvaraj.types.element.ElementCommandResult; 10 | import org.openqa.selenium.JavascriptExecutor; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.Objects; 15 | import java.util.Scanner; 16 | 17 | public class WowXHRScriptExecutor 18 | implements ScriptExecutor, SpyDriverListener { 19 | 20 | private final T javaScriptExecutor; 21 | private final List stateChangeListener = new ArrayList<>(); 22 | private static final String wowXhr; 23 | 24 | static { 25 | wowXhr = readScriptFromResource("/js/wowxhr.js"); 26 | } 27 | 28 | public WowXHRScriptExecutor(T javaScriptExecutor) { 29 | this.javaScriptExecutor = javaScriptExecutor; 30 | injectMock(); 31 | } 32 | 33 | @Override 34 | public void beforeDriverCommandExecuted(DriverCommand command) { 35 | } 36 | 37 | @Override 38 | public void afterDriverCommandExecuted(DriverCommandResult command) { 39 | if (command.getMethod().getName() 40 | .matches("get|to|refresh|forward|back|executeScript|executeScriptAsync|perform")) { 41 | onDriverStateChanged(); 42 | } 43 | } 44 | 45 | @Override 46 | public void onException(DriverCommandException command) { 47 | } 48 | 49 | @Override 50 | public void beforeElementCommandExecuted(ElementCommand command) { 51 | } 52 | 53 | @Override 54 | public void afterElementCommandExecuted(ElementCommandResult command) { 55 | if (command.getMethod().getName().matches("click")) { 56 | onDriverStateChanged(); 57 | } 58 | } 59 | 60 | @Override 61 | public void onException(ElementCommandException command) { 62 | } 63 | 64 | @Override 65 | public Object executeScript(String script, Object... args) { 66 | return javaScriptExecutor.executeScript(script, args); 67 | } 68 | 69 | @Override 70 | public Object executeAsyncScript(String script, Object... args) { 71 | return javaScriptExecutor.executeAsyncScript(script, args); 72 | } 73 | 74 | @Override 75 | public void addStateChangeListener(DriverStateChangeListener listener) { 76 | this.stateChangeListener.add(listener); 77 | } 78 | 79 | private void onDriverStateChanged() { 80 | notifyStateChange(injectMock()); 81 | } 82 | 83 | private void notifyStateChange(Boolean scriptInjected) { 84 | this.stateChangeListener.forEach(l -> l.onStateChanged(scriptInjected)); 85 | } 86 | 87 | private static String readScriptFromResource(String fileName) { 88 | return new Scanner(Objects.requireNonNull(WowXHRScriptExecutor.class.getResourceAsStream(fileName)), "UTF-8").useDelimiter("\\A").next(); 89 | } 90 | 91 | public Boolean injectMock() { 92 | Boolean isScriptAlreadyInjected = (Boolean) executeScript("return !!window.xhook"); 93 | if (!isScriptAlreadyInjected) { 94 | executeScript(wowXhr); 95 | return true; 96 | } else { 97 | return false; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/exceptions/DriverNotSupportedException.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr.exceptions; 2 | 3 | public class DriverNotSupportedException extends Exception { 4 | 5 | public DriverNotSupportedException(String driverClassName) { 6 | super("Drivers doesn't support javascript execution. Provided driver : " + driverClassName); 7 | } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/log/DateSerializer.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr.log; 2 | 3 | import com.fasterxml.jackson.core.JsonParser; 4 | import com.fasterxml.jackson.core.JsonProcessingException; 5 | import com.fasterxml.jackson.databind.DeserializationContext; 6 | import com.fasterxml.jackson.databind.deser.std.StdDeserializer; 7 | 8 | import java.io.IOException; 9 | import java.util.Date; 10 | 11 | public class DateSerializer extends StdDeserializer { 12 | 13 | public DateSerializer(){ 14 | super(Date.class); 15 | } 16 | 17 | protected DateSerializer(Class vc) { 18 | super(vc); 19 | } 20 | 21 | @Override 22 | public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException { 23 | try { 24 | return new Date(p.getLongValue()); 25 | } catch (Exception e ){ 26 | 27 | } 28 | return null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/log/Log.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr.log; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.github.sudharsan_selvaraj.wowxhr.JsExecutable; 5 | import io.github.sudharsan_selvaraj.wowxhr.ScriptExecutor; 6 | import io.github.sudharsan_selvaraj.wowxhr.ScriptProvider; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class Log extends JsExecutable { 12 | 13 | public Log(ScriptExecutor scriptExecutor) { 14 | super(scriptExecutor); 15 | } 16 | 17 | public void clearLogs() { 18 | executeScript(ScriptProvider.getFunction("clearLogs")); 19 | } 20 | 21 | public List getXHRLogs() { 22 | return getXHRLogs(true); 23 | } 24 | 25 | public List getXHRLogs(Boolean clearExisting) { 26 | ObjectMapper mapper = new ObjectMapper(); 27 | try { 28 | return mapper.convertValue(executeScript("return (function(clearLogs){" + 29 | ScriptProvider.getFunction("getLogs") 30 | + "})" + "(arguments[0])", 31 | clearExisting), 32 | mapper.getTypeFactory().constructCollectionType(List.class, XHRLog.class)); 33 | } catch (Exception e) { 34 | return new ArrayList<>(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/log/RequestLog.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr.log; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import io.github.sudharsan_selvaraj.wowxhr.HttpMethod; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | import java.util.Map; 10 | 11 | @Getter 12 | @Setter 13 | @JsonIgnoreProperties(ignoreUnknown = true) 14 | public class RequestLog { 15 | 16 | private HttpMethod method; 17 | 18 | private String url; 19 | 20 | private Object body; 21 | 22 | private Map headers; 23 | 24 | @JsonProperty("type") 25 | private String responseType; 26 | 27 | private boolean withCredentials; 28 | 29 | } -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/log/ResponseLog.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr.log; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import java.util.Map; 9 | 10 | @Getter 11 | @Setter 12 | @JsonIgnoreProperties(ignoreUnknown = true) 13 | public class ResponseLog { 14 | 15 | private Integer status; 16 | 17 | private String statusText; 18 | 19 | private String finalUrl; 20 | 21 | private String text; 22 | 23 | private Map headers; 24 | 25 | @JsonProperty("data") 26 | private Object body; 27 | 28 | } -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/log/XHRLog.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr.log; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | import java.util.Date; 10 | 11 | @Getter 12 | @Setter 13 | @JsonIgnoreProperties(ignoreUnknown = true) 14 | public class XHRLog { 15 | 16 | @JsonProperty("_id") 17 | private String id; 18 | 19 | @JsonDeserialize(using = DateSerializer.class) 20 | private Date initiatedTime; 21 | 22 | @JsonDeserialize(using = DateSerializer.class) 23 | private Date completedTime; 24 | 25 | private RequestLog request; 26 | 27 | protected ResponseLog response; 28 | } 29 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/mock/Mock.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr.mock; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.github.sudharsan_selvaraj.wowxhr.*; 5 | 6 | import java.util.*; 7 | import java.util.stream.Collectors; 8 | 9 | public class Mock extends JsExecutable { 10 | 11 | private final List mockableRequest; 12 | 13 | public Mock(ScriptExecutor scriptExecutor) { 14 | super(scriptExecutor); 15 | mockableRequest = new ArrayList<>(); 16 | 17 | scriptExecutor.addStateChangeListener(getListener()); 18 | } 19 | 20 | public Mock add(Mockable mockable) { 21 | mockableRequest.add(mockable); 22 | injectRequest(mockable); 23 | return this; 24 | } 25 | 26 | public void pause() { 27 | executeScript("(function(){"+ ScriptProvider.getFunction("pauseMocking") +"})()"); 28 | } 29 | 30 | public void resume() { 31 | executeScript("(function(){"+ ScriptProvider.getFunction("resumeMocking") +"})()"); 32 | } 33 | 34 | private void injectRequest(Mockable mockableRequest) { 35 | injectRequest(Collections.singletonList(mockableRequest)); 36 | } 37 | 38 | private void injectRequest(List mockableRequests) { 39 | if (!mockableRequests.isEmpty()) { 40 | System.out.println("Injecting mock requests"); 41 | List args = mockableRequests.stream().map(this::convertMockableToJSON).collect(Collectors.toList()); 42 | executeScript("(function(newMocks){"+ ScriptProvider.getFunction("injectMock") +"})(arguments[0])", args); 43 | } 44 | } 45 | 46 | private void onDriverStateChanged() { 47 | injectRequest(this.mockableRequest); 48 | } 49 | 50 | private String convertMockableToJSON(Mockable request) { 51 | 52 | Map mockRequest = new HashMap(){{ 53 | put("headers", request.getMockHttpRequest().getHeaders()); 54 | put("body", request.getMockHttpRequest().getBody()); 55 | put("queryParams", request.getMockHttpRequest().getQueryParam()); 56 | }}; 57 | 58 | Map mockResponse = new HashMap() {{ 59 | put("headers", request.getMockHttpResponse().getHeaders()); 60 | put("body", request.getMockHttpResponse().getBody()); 61 | put("status", request.getMockHttpResponse().getStatus()); 62 | put("delay", request.getMockHttpResponse().getDelay()); 63 | }}; 64 | 65 | Map payload = new HashMap() {{ 66 | put("id", request.getId()); 67 | put("url", request.getUrl()); 68 | put("method", request.getMethod()); 69 | put("queryParams", request.getQueryParam()); 70 | put("mockRequest", mockRequest); 71 | put("mockResponse", mockResponse); 72 | }}; 73 | 74 | try { 75 | return new ObjectMapper().writeValueAsString(payload); 76 | } catch (Exception e) { 77 | return "{}"; 78 | } 79 | } 80 | 81 | private DriverStateChangeListener getListener() { 82 | return new DriverStateChangeListener() { 83 | @Override 84 | public void onStateChanged(Boolean scriptInjected) { 85 | if(scriptInjected) { 86 | onDriverStateChanged(); 87 | } 88 | } 89 | }; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/mock/MockHttpRequest.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr.mock; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | @Getter 9 | public class MockHttpRequest { 10 | 11 | private Map headers; 12 | private Map queryParam; 13 | private String body; 14 | 15 | public MockHttpRequest() { 16 | headers = new HashMap<>(); 17 | queryParam = new HashMap<>(); 18 | } 19 | 20 | public static MockHttpRequest mockRequest() { 21 | return new MockHttpRequest(); 22 | } 23 | 24 | public MockHttpRequest setHeader(Map headers) { 25 | this.headers = headers; 26 | return this; 27 | } 28 | 29 | public MockHttpRequest setHeader(String name, String value) { 30 | this.headers.put(name, value); 31 | return this; 32 | } 33 | 34 | public MockHttpRequest setQueryParam(String name, Object value) { 35 | this.queryParam.put(name, value); 36 | return this; 37 | } 38 | 39 | public MockHttpRequest setQueryParam(Map queryParam) { 40 | this.queryParam.putAll(queryParam); 41 | return this; 42 | } 43 | 44 | public MockHttpRequest setBody(String body) { 45 | this.body = body; 46 | return this; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/mock/MockHttpResponse.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr.mock; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import lombok.Getter; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | @Getter 10 | public class MockHttpResponse { 11 | 12 | private Map headers; 13 | private String body; 14 | private Integer delay; 15 | private Integer status; 16 | 17 | public MockHttpResponse() { 18 | this.headers = new HashMap<>(); 19 | } 20 | 21 | public MockHttpResponse withHeader(Map headers) { 22 | this.headers = headers; 23 | return this; 24 | } 25 | 26 | public MockHttpResponse withHeader(String name, String value) { 27 | this.headers.put(name, value); 28 | return this; 29 | } 30 | 31 | public MockHttpResponse withStatus(Integer status) { 32 | this.status = status; 33 | return this; 34 | } 35 | 36 | public MockHttpResponse withBody(String body) { 37 | this.body = body; 38 | return this; 39 | } 40 | 41 | public MockHttpResponse withBody(Object body) { 42 | try { 43 | this.body = new ObjectMapper().writeValueAsString(body); 44 | } catch (Exception e) { 45 | e.printStackTrace(); 46 | } 47 | return this; 48 | } 49 | 50 | public MockHttpResponse withDelay(Integer delayInSeconds) { 51 | this.delay = delayInSeconds; 52 | return this; 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /projects/selenium-java/src/main/java/io/github/sudharsan_selvaraj/wowxhr/mock/Mockable.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.wowxhr.mock; 2 | 3 | import io.github.sudharsan_selvaraj.wowxhr.HttpMethod; 4 | import lombok.Getter; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.UUID; 9 | @Getter 10 | public class Mockable { 11 | 12 | private final HttpMethod method; 13 | private final String url; 14 | private Map queryParam; 15 | private MockHttpRequest mockHttpRequest; 16 | private MockHttpResponse mockHttpResponse; 17 | private final String id; 18 | 19 | private Mockable(HttpMethod method, String url) { 20 | this.id = UUID.randomUUID().toString(); 21 | this.method = method; 22 | this.url = url; 23 | this.queryParam = new HashMap<>(); 24 | this.mockHttpRequest = new MockHttpRequest(); 25 | this.mockHttpResponse = new MockHttpResponse(); 26 | } 27 | 28 | public static Mockable whenGET(String url) { 29 | return new Mockable(HttpMethod.GET, url); 30 | } 31 | 32 | public static Mockable whenPOST(String url) { 33 | return new Mockable(HttpMethod.POST, url); 34 | } 35 | 36 | public static Mockable whenPATCH(String url) { 37 | return new Mockable(HttpMethod.PATCH, url); 38 | } 39 | 40 | public static Mockable whenPUT(String url) { 41 | return new Mockable(HttpMethod.PUT, url); 42 | } 43 | 44 | public static Mockable whenDELETE(String url) { 45 | return new Mockable(HttpMethod.DELETE, url); 46 | } 47 | 48 | public static MockHttpRequest mockRequest() { 49 | return new MockHttpRequest(); 50 | } 51 | 52 | public static MockHttpResponse mockResponse() { 53 | return new MockHttpResponse(); 54 | } 55 | 56 | public Mockable withQueryParam(Map params) { 57 | this.queryParam = params; 58 | return this; 59 | } 60 | 61 | public Mockable withQueryParam(String name, String value) { 62 | this.queryParam.put(name, value); 63 | return this; 64 | } 65 | 66 | public Mockable modifyRequest(MockHttpRequest mockHttpRequest) { 67 | this.mockHttpRequest = mockHttpRequest; 68 | return this; 69 | } 70 | 71 | public Mockable respond(MockHttpResponse mockHttpResponse) { 72 | this.mockHttpResponse = mockHttpResponse; 73 | return this; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /projects/selenium-java/src/main/resources/js/scripts.js: -------------------------------------------------------------------------------- 1 | ///////////////////////// 2 | // Mock related functions 3 | ////////////////////////// 4 | 5 | function injectMock(newMocks) { 6 | if (new RegExp("^(data|about:)").test(document.location.toString())) { 7 | return; 8 | } 9 | 10 | var MOCK_STORAGE_KEY = "_wow_xhr_mock_"; 11 | var existingMocks = JSON.parse(window.sessionStorage.getItem(MOCK_STORAGE_KEY) || "[]"); 12 | var existingMockIds = {}; 13 | existingMocks.forEach(function (eMock) { 14 | existingMockIds[JSON.parse(eMock).id] = true; 15 | }); 16 | var mocksTobeAdded = existingMocks.concat(newMocks.filter((m) => !existingMockIds[JSON.parse(m).id])); 17 | window.sessionStorage.setItem(MOCK_STORAGE_KEY, JSON.stringify(mocksTobeAdded)) 18 | } 19 | 20 | function pauseMocking() { 21 | window.sessionStorage.setItem("_wow_xhr_mock_paused_", "true"); 22 | } 23 | 24 | function resumeMocking() { 25 | window.sessionStorage.setItem("_wow_xhr_mock_paused_", "false"); 26 | } 27 | 28 | ///////////////////////// 29 | // Logs related functions 30 | ////////////////////////// 31 | 32 | function getLogs(clearLogs) { 33 | let COMPLETED_CALLS_STORAGE_KEY = "_wow_xhr_log_completed_calls_"; 34 | try { 35 | let logs = JSON.parse(window.sessionStorage.getItem(COMPLETED_CALLS_STORAGE_KEY || "[]")); 36 | if (clearLogs) { 37 | window.sessionStorage.setItem(COMPLETED_CALLS_STORAGE_KEY, "[]"); 38 | } 39 | return logs; 40 | } catch (err) { 41 | return []; 42 | } 43 | } 44 | 45 | function clearLogs() { 46 | window.sessionStorage.setItem("_wow_xhr_log_completed_calls_", "[]"); 47 | } 48 | 49 | ///////////////////////// 50 | // Utility functions 51 | ////////////////////////// 52 | 53 | function waitForPageLoad() { 54 | return !(new RegExp("^(data|about:)").test(document.location.toString())) && 55 | document.readyState == "complete"; 56 | } -------------------------------------------------------------------------------- /projects/selenium-java/src/main/resources/js/wowxhr.js: -------------------------------------------------------------------------------- 1 | (()=>{var e={788:function(){(function(e){var t,n,r,o,s,a,i,u,c,f,l,d,p,h,y,g,v,m,w,b,x,S,E,R,_,k,O,T,C=[].indexOf||function(e){for(var t=0,n=this.length;t=0||(s=s===e?r[t].length:s,r[t].splice(s,0,n))},n[c]=function(t,n){var s;t!==e?(n===e&&(r[t]=[]),-1!==(s=o(t).indexOf(n))&&o(t).splice(s,1)):r={}},n[s]=function(){var e,r,s,a,i,u,c;for(r=(e=_(arguments)).shift(),t||(e[0]=x(e[0],b(r))),(a=n["on"+r])&&a.apply(n,e),s=i=0,u=(c=o(r).concat(o("*"))).length;i2)throw"invalid hook";return O[f](n,e,t)},O.after=function(e,n){if(e.length<2||e.length>3)throw"invalid hook";return O[f](t,e,n)},O.enable=function(){d[g]=y,"function"==typeof p&&(d.fetch=p),i&&(d.FormData=h)},O.disable=function(){d[g]=O[g],d.fetch=O.fetch,i&&(d.FormData=i)},v=O.headers=function(e,t){var n,r,o,s,a,i,u,c,f;switch(null==t&&(t={}),typeof e){case"object":for(o in r=[],e)a=e[o],s=o.toLowerCase(),r.push(s+":\t"+a);return r.join("\n")+"\n";case"string":for(u=0,c=(r=e.split("\n")).length;ue&&e<4;)c.readyState=++e,1===e&&c[s]("loadstart",{}),2===e&&N(),4===e&&(N(),L()),c[s]("readystatechange",{}),4===e&&(!1===w.async?a():setTimeout(a,0))},a=function(){d||c[s]("load",{}),c[s]("loadend",{}),d&&(c.readyState=0)},e=0,_=function(e){var n,r;4===e?(n=O.listeners(t),(r=function(){var e;return n.length?2===(e=n.shift()).length?(e(w,b),r()):3===e.length&&w.async?e(w,b,r):r():i(4)})()):i(e)},c=(w={}).xhr=o(),I.onreadystatechange=function(e){try{2===I.readyState&&m()}catch(e){}4===I.readyState&&(T=!1,m(),y()),_(I.readyState)},p=function(){d=!0},c[f]("error",p),c[f]("timeout",p),c[f]("abort",p),c[f]("progress",(function(){e<3?_(3):c[s]("readystatechange",{})})),("withCredentials"in I||O.addWithCredentials)&&(c.withCredentials=!1),c.status=0;for(P=0,D=(H=r.concat(l)).length;P{"use strict";var e,t=function(){},r="_wow_xhr_log_pending_calls_",o="_wow_xhr_log_completed_calls_",s=window,a=function(){function e(){try{[r,o].forEach((function(e){s.sessionStorage.getItem(e)||s.sessionStorage.setItem(e,"[]")}))}catch(e){console.log("Unable to set session storage")}}return e.prototype.beforeXHR=function(e){var n=e.wow_xhr_id,o=new t;o.id=n,o.request=e,o.initiatedTime=Date.now(),o.pending=!0;var a=JSON.parse(s.sessionStorage.getItem(r)||"[]");return a.push(o),s.sessionStorage.setItem(r,JSON.stringify(a)),Promise.resolve()},e.prototype.afterXHR=function(e,t){var n=e.wow_xhr_id,a=JSON.parse(s.sessionStorage.getItem(r)||"[]"),i=a.findIndex((function(e){return e.id==n})),u=a[i];u.response=t,u.completedTime=Date.now(),u.pending=!1;var c=JSON.parse(s.sessionStorage.getItem(o)||"[]");return c.push(u),this.setCompletedCalls(c),this.setPendingCalls(a),Promise.resolve(void 0)},e.prototype.setCompletedCalls=function(e){s.sessionStorage.setItem(o,JSON.stringify(e))},e.prototype.setPendingCalls=function(e){s.sessionStorage.setItem(r,JSON.stringify(e))},e}(),i="_wow_xhr_mock_",u="_wow_xhr_mock_paused_",c=window,f=function(){function e(){try{c.sessionStorage.getItem(i)||c.sessionStorage.setItem(i,"[]"),c.sessionStorage.getItem(u)||c.sessionStorage.setItem(u,"false")}catch(e){console.log("Unable set session storage")}}return e.prototype.findMock=function(e){var t=JSON.parse(c.sessionStorage.getItem(i)||"[]");if("true"!=c.sessionStorage.getItem(u))return t.map((function(e){return JSON.parse(e)})).filter((function(t){return t.method===e.method&&new RegExp(t.url).test(e.url)}))},e.prototype.beforeXHR=function(e){var t=this;return new Promise((function(n,r){var o=e.method,s=e.url;t.findMock({method:o,url:s}).forEach((function(t){if(t&&t.mockRequest){var n=t.mockRequest,r=n.headers,s=n.body,a=n.queryParams;if(r&&Object.assign(e.headers,r),s&&new RegExp(o).test("POST|PUT|PATCH")&&(e.body=s),a&&Object.keys(a).length){var i=new URL(e.url);i.searchParams.forEach((function(e,t){a.hasOwnProperty(t)&&(i.searchParams.set(t,a[t]),delete a[t])})),Object.keys(a).forEach((function(e){i.searchParams.append(e,a[e])})),e.url=i.toString()}}})),n()}))},e.prototype.afterXHR=function(e,t){var n=this;return new Promise((function(r,o){var s=e.method,a=e.url,i=n.findMock({method:s,url:a}),u=0;console.log("After xhr:"),console.log(e),console.log("Mocks:"),console.log(i),i.forEach((function(e){if(e&&e.mockResponse){var n=e.mockResponse,r=n.headers,o=n.body,s=n.status,a=n.delay;r&&Object.assign(t.headers,r),o&&(t.text=t.data=o),s&&(t.status=s),a&&(u=1e3*a)}})),setTimeout(r,u)}))},e}(),l=new Uint8Array(16);function d(){if(!e&&!(e="undefined"!=typeof crypto&&crypto.getRandomValues&&crypto.getRandomValues.bind(crypto)||"undefined"!=typeof msCrypto&&"function"==typeof msCrypto.getRandomValues&&msCrypto.getRandomValues.bind(msCrypto)))throw new Error("crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported");return e(l)}const p=/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i,h=function(e){return"string"==typeof e&&p.test(e)};for(var y=[],g=0;g<256;++g)y.push((g+256).toString(16).substr(1));const v=function(e,t,n){var r=(e=e||{}).random||(e.rng||d)();if(r[6]=15&r[6]|64,r[8]=63&r[8]|128,t){n=n||0;for(var o=0;o<16;++o)t[n+o]=r[o];return t}return function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=(y[e[t+0]]+y[e[t+1]]+y[e[t+2]]+y[e[t+3]]+"-"+y[e[t+4]]+y[e[t+5]]+"-"+y[e[t+6]]+y[e[t+7]]+"-"+y[e[t+8]]+y[e[t+9]]+"-"+y[e[t+10]]+y[e[t+11]]+y[e[t+12]]+y[e[t+13]]+y[e[t+14]]+y[e[t+15]]).toLowerCase();if(!h(n))throw TypeError("Stringified UUID is invalid");return n}(r)};var m=function(e,t,n,r){return new(n||(n=Promise))((function(o,s){function a(e){try{u(r.next(e))}catch(e){s(e)}}function i(e){try{u(r.throw(e))}catch(e){s(e)}}function u(e){var t;e.done?o(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(a,i)}u((r=r.apply(e,t||[])).next())}))},w=function(e,t){var n,r,o,s,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return s={next:i(0),throw:i(1),return:i(2)},"function"==typeof Symbol&&(s[Symbol.iterator]=function(){return this}),s;function i(s){return function(i){return function(s){if(n)throw new TypeError("Generator is already executing.");for(;a;)try{if(n=1,r&&(o=2&s[0]?r.return:s[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,s[1])).done)return o;switch(r=0,o&&(s=[2&s[0],o.value]),s[0]){case 0:case 1:o=s;break;case 4:return a.label++,{value:s[1],done:!1};case 5:a.label++,r=s[1],s=[0];continue;case 7:s=a.ops.pop(),a.trys.pop();continue;default:if(!((o=(o=a.trys).length>0&&o[o.length-1])||6!==s[0]&&2!==s[0])){a=0;continue}if(3===s[0]&&(!o||s[1]>o[0]&&s[1] wowXHRThreadLocal = new ThreadLocal<>(); 26 | 27 | @BeforeSuite 28 | public void initWebDriver() { 29 | WebDriverManager.chromedriver().setup(); 30 | WebDriverManager.firefoxdriver().setup(); 31 | } 32 | 33 | @BeforeMethod 34 | @Parameters({"browser"}) 35 | public void setupDriver(String browser) throws DriverNotSupportedException { 36 | WebDriver driver; 37 | switch (browser) { 38 | case "firefox": 39 | FirefoxOptions ffoptions = new FirefoxOptions(); 40 | ffoptions.setPageLoadStrategy(PageLoadStrategy.NORMAL); 41 | driver = new FirefoxDriver(ffoptions); 42 | break; 43 | default: 44 | ChromeOptions options = new ChromeOptions(); 45 | driver = new ChromeDriver(options); 46 | } 47 | wowXHRThreadLocal.set(new WowXHR(driver)); 48 | } 49 | 50 | 51 | @AfterMethod(alwaysRun = true) 52 | public void tearDown() { 53 | if (wowXHRThreadLocal.get().getMockDriver() != null) { 54 | wowXHRThreadLocal.get().getMockDriver().quit(); 55 | } 56 | } 57 | 58 | protected T readListFromJson(String filePath, Class responseClass) { 59 | ObjectMapper mapper = new ObjectMapper(); 60 | try { 61 | return mapper.readValue(readScriptFromResource(filePath), responseClass); 62 | } catch (JsonProcessingException e) { 63 | e.printStackTrace(); 64 | return null; 65 | } 66 | } 67 | 68 | private static String readScriptFromResource(String fileName) { 69 | return new Scanner(Objects.requireNonNull(WowXHRScriptExecutor.class.getResourceAsStream(fileName)), "UTF-8").useDelimiter("\\A").next(); 70 | } 71 | 72 | protected void sleep(int millisec) { 73 | try { 74 | Thread.sleep(millisec); 75 | } catch (Exception e) { 76 | // 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /projects/selenium-java/src/test/java/io/github/sudharsan_selvaraj/e2e/admin_panel/Color.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.e2e.admin_panel; 2 | 3 | public enum Color { 4 | //Color end string, color reset 5 | RESET("\033[0m"), 6 | 7 | // Regular Colors. Normal color, no bold, background color etc. 8 | BLACK("\033[0;30m"), // BLACK 9 | RED("\033[0;31m"), // RED 10 | GREEN("\033[0;32m"), // GREEN 11 | YELLOW("\033[0;33m"), // YELLOW 12 | BLUE("\033[0;34m"), // BLUE 13 | MAGENTA("\033[0;35m"), // MAGENTA 14 | CYAN("\033[0;36m"), // CYAN 15 | WHITE("\033[0;37m"), // WHITE 16 | 17 | // Bold 18 | BLACK_BOLD("\033[1;30m"), // BLACK 19 | RED_BOLD("\033[1;31m"), // RED 20 | GREEN_BOLD("\033[1;32m"), // GREEN 21 | YELLOW_BOLD("\033[1;33m"), // YELLOW 22 | BLUE_BOLD("\033[1;34m"), // BLUE 23 | MAGENTA_BOLD("\033[1;35m"), // MAGENTA 24 | CYAN_BOLD("\033[1;36m"), // CYAN 25 | WHITE_BOLD("\033[1;37m"), // WHITE 26 | 27 | // Underline 28 | BLACK_UNDERLINED("\033[4;30m"), // BLACK 29 | RED_UNDERLINED("\033[4;31m"), // RED 30 | GREEN_UNDERLINED("\033[4;32m"), // GREEN 31 | YELLOW_UNDERLINED("\033[4;33m"), // YELLOW 32 | BLUE_UNDERLINED("\033[4;34m"), // BLUE 33 | MAGENTA_UNDERLINED("\033[4;35m"), // MAGENTA 34 | CYAN_UNDERLINED("\033[4;36m"), // CYAN 35 | WHITE_UNDERLINED("\033[4;37m"), // WHITE 36 | 37 | // Background 38 | BLACK_BACKGROUND("\033[40m"), // BLACK 39 | RED_BACKGROUND("\033[41m"), // RED 40 | GREEN_BACKGROUND("\033[42m"), // GREEN 41 | YELLOW_BACKGROUND("\033[43m"), // YELLOW 42 | BLUE_BACKGROUND("\033[44m"), // BLUE 43 | MAGENTA_BACKGROUND("\033[45m"), // MAGENTA 44 | CYAN_BACKGROUND("\033[46m"), // CYAN 45 | WHITE_BACKGROUND("\033[47m"), // WHITE 46 | 47 | // High Intensity 48 | BLACK_BRIGHT("\033[0;90m"), // BLACK 49 | RED_BRIGHT("\033[0;91m"), // RED 50 | GREEN_BRIGHT("\033[0;92m"), // GREEN 51 | YELLOW_BRIGHT("\033[0;93m"), // YELLOW 52 | BLUE_BRIGHT("\033[0;94m"), // BLUE 53 | MAGENTA_BRIGHT("\033[0;95m"), // MAGENTA 54 | CYAN_BRIGHT("\033[0;96m"), // CYAN 55 | WHITE_BRIGHT("\033[0;97m"), // WHITE 56 | 57 | // Bold High Intensity 58 | BLACK_BOLD_BRIGHT("\033[1;90m"), // BLACK 59 | RED_BOLD_BRIGHT("\033[1;91m"), // RED 60 | GREEN_BOLD_BRIGHT("\033[1;92m"), // GREEN 61 | YELLOW_BOLD_BRIGHT("\033[1;93m"), // YELLOW 62 | BLUE_BOLD_BRIGHT("\033[1;94m"), // BLUE 63 | MAGENTA_BOLD_BRIGHT("\033[1;95m"), // MAGENTA 64 | CYAN_BOLD_BRIGHT("\033[1;96m"), // CYAN 65 | WHITE_BOLD_BRIGHT("\033[1;97m"), // WHITE 66 | 67 | // High Intensity backgrounds 68 | BLACK_BACKGROUND_BRIGHT("\033[0;100m"), // BLACK 69 | RED_BACKGROUND_BRIGHT("\033[0;101m"), // RED 70 | GREEN_BACKGROUND_BRIGHT("\033[0;102m"), // GREEN 71 | YELLOW_BACKGROUND_BRIGHT("\033[0;103m"), // YELLOW 72 | BLUE_BACKGROUND_BRIGHT("\033[0;104m"), // BLUE 73 | MAGENTA_BACKGROUND_BRIGHT("\033[0;105m"), // MAGENTA 74 | CYAN_BACKGROUND_BRIGHT("\033[0;106m"), // CYAN 75 | WHITE_BACKGROUND_BRIGHT("\033[0;107m"); // WHITE 76 | 77 | private final String code; 78 | 79 | Color(String code) { 80 | this.code = code; 81 | } 82 | 83 | @Override 84 | public String toString() { 85 | return code; 86 | } 87 | } -------------------------------------------------------------------------------- /projects/selenium-java/src/test/java/io/github/sudharsan_selvaraj/e2e/admin_panel/models/Address.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.e2e.admin_panel.models; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @AllArgsConstructor 10 | @NoArgsConstructor 11 | @Getter 12 | @Setter 13 | @JsonIgnoreProperties(ignoreUnknown = true) 14 | public class Address { 15 | private String city; 16 | private String street; 17 | private String suite; 18 | private String zipcode; 19 | } 20 | -------------------------------------------------------------------------------- /projects/selenium-java/src/test/java/io/github/sudharsan_selvaraj/e2e/admin_panel/models/Post.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.e2e.admin_panel.models; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @Getter 9 | @Setter 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | public class Post { 13 | 14 | private Integer userId; 15 | private Integer id; 16 | private String title; 17 | private String body; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /projects/selenium-java/src/test/java/io/github/sudharsan_selvaraj/e2e/admin_panel/models/User.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.e2e.admin_panel.models; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @Getter 10 | @Setter 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @JsonIgnoreProperties(ignoreUnknown = true) 14 | public class User { 15 | 16 | private Integer id; 17 | private String email; 18 | private String name; 19 | private String phone; 20 | private String username; 21 | private String website; 22 | private Address address; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /projects/selenium-java/src/test/java/io/github/sudharsan_selvaraj/e2e/admin_panel/pages/BasePage.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.e2e.admin_panel.pages; 2 | import org.checkerframework.checker.nullness.compatqual.NullableDecl; 3 | import org.openqa.selenium.By; 4 | import org.openqa.selenium.WebDriver; 5 | import org.openqa.selenium.support.PageFactory; 6 | import org.openqa.selenium.support.ui.ExpectedCondition; 7 | import org.openqa.selenium.support.ui.WebDriverWait; 8 | 9 | public class BasePage { 10 | protected WebDriver driver; 11 | 12 | public BasePage(WebDriver driver) { 13 | PageFactory.initElements(driver, this); 14 | this.driver = driver; 15 | } 16 | 17 | public void waitForLoading() { 18 | new WebDriverWait(driver, 30) 19 | .until(new ExpectedCondition() { 20 | @NullableDecl 21 | @Override 22 | public Boolean apply(@NullableDecl WebDriver input) { 23 | return input.findElements(By.id("nprogress")).isEmpty(); 24 | } 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /projects/selenium-java/src/test/java/io/github/sudharsan_selvaraj/e2e/admin_panel/pages/DashBoard.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.e2e.admin_panel.pages; 2 | 3 | import lombok.Getter; 4 | import org.openqa.selenium.By; 5 | import org.openqa.selenium.WebDriver; 6 | import org.openqa.selenium.WebElement; 7 | import org.openqa.selenium.support.FindBy; 8 | 9 | public class DashBoard extends BasePage { 10 | 11 | @FindBy(xpath = ".//*[contains(@class,'panel-default')][.//a[contains(.,'Users')]]") 12 | private WebElement userPanel; 13 | 14 | @FindBy(xpath = ".//*[contains(@class,'panel-default')][.//a[contains(.,'Users')]]") 15 | private WebElement postsPanel; 16 | 17 | @FindBy(css = ".navbar-header a") 18 | private WebElement navBarLogo; 19 | 20 | @Getter 21 | private Table userTable, postsTable; 22 | 23 | public DashBoard(WebDriver driver) { 24 | super(driver); 25 | userTable = new Table(userPanel); 26 | postsTable = new Table(postsPanel); 27 | } 28 | 29 | public DashBoard clickUserMenu() { 30 | return clickMenu("Users"); 31 | } 32 | 33 | public DashBoard clickPostsMenu() { 34 | return clickMenu("Posts"); 35 | } 36 | 37 | public DashBoard moveToDashboard() { 38 | driver.findElement(By.cssSelector(".navbar-brand")).click(); 39 | waitForLoading(); 40 | return this; 41 | } 42 | 43 | public DashBoard clickLogo() { 44 | navBarLogo.click(); 45 | waitForLoading(); 46 | return this; 47 | } 48 | 49 | private DashBoard clickMenu(String menu) { 50 | driver.findElement(By.cssSelector("#side-menu")).findElement(By.partialLinkText(menu)).click(); 51 | waitForLoading(); 52 | return this; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /projects/selenium-java/src/test/java/io/github/sudharsan_selvaraj/e2e/admin_panel/pages/LoginPage.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.e2e.admin_panel.pages; 2 | 3 | 4 | import org.openqa.selenium.By; 5 | import org.openqa.selenium.WebDriver; 6 | import org.openqa.selenium.WebElement; 7 | import org.openqa.selenium.support.FindBy; 8 | import org.openqa.selenium.support.ui.ExpectedConditions; 9 | import org.openqa.selenium.support.ui.WebDriverWait; 10 | 11 | public class LoginPage extends BasePage { 12 | 13 | @FindBy(id = "username") 14 | private WebElement usernameEle; 15 | 16 | @FindBy(id = "password") 17 | private WebElement passwordEle; 18 | 19 | @FindBy(css = "input[type='button']") 20 | private WebElement loginButton; 21 | 22 | public LoginPage(WebDriver driver) { 23 | super(driver); 24 | } 25 | 26 | public void login(String username, String password) { 27 | usernameEle.clear(); 28 | usernameEle.sendKeys(username); 29 | 30 | passwordEle.clear(); 31 | passwordEle.sendKeys(password); 32 | loginButton.click(); 33 | 34 | waitForLogin(); 35 | } 36 | 37 | public void waitForLogin() { 38 | new WebDriverWait(driver, 30) 39 | .until(ExpectedConditions.invisibilityOf(driver.findElement(By.cssSelector(".loading")))); 40 | } 41 | 42 | @Override 43 | public void waitForLoading() { 44 | new WebDriverWait(driver, 30) 45 | .until(ExpectedConditions.presenceOfElementLocated(By.id("username"))); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /projects/selenium-java/src/test/java/io/github/sudharsan_selvaraj/e2e/admin_panel/pages/Table.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.e2e.admin_panel.pages; 2 | 3 | import org.openqa.selenium.By; 4 | import org.openqa.selenium.WebElement; 5 | 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | public class Table { 12 | 13 | private WebElement parent; 14 | private List headers; 15 | private static By tableLocator = By.cssSelector("table.table"); 16 | 17 | public Table(WebElement parent) { 18 | this.parent = parent; 19 | this.headers = new ArrayList<>(); 20 | } 21 | 22 | public List getHeaders() { 23 | if (headers.isEmpty()) { 24 | this.headers = getTable() 25 | .findElements(By.tagName("th")) 26 | .stream() 27 | .map(WebElement::getText) 28 | .collect(Collectors.toList()); 29 | } 30 | return headers; 31 | } 32 | 33 | public String getColumnValue(String columnName, Integer rowIndex) { 34 | return getTable() 35 | .findElement(By.xpath(".//tbody/tr[" + (rowIndex) + "]/descendant::td[" + (getHeaders().indexOf(columnName) + 1) + "]")) 36 | .getText(); 37 | } 38 | 39 | private WebElement getTable() { 40 | return parent.findElement(tableLocator); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /projects/selenium-java/src/test/java/io/github/sudharsan_selvaraj/e2e/admin_panel/tests/NoMockTest.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.e2e.admin_panel.tests; 2 | 3 | import io.github.sudharsan_selvaraj.e2e.admin_panel.BaseWebDriverTest; 4 | import io.github.sudharsan_selvaraj.e2e.admin_panel.pages.DashBoard; 5 | import io.github.sudharsan_selvaraj.e2e.admin_panel.pages.Table; 6 | import io.github.sudharsan_selvaraj.wowxhr.WowXHR; 7 | import org.openqa.selenium.WebDriver; 8 | import org.testng.annotations.Parameters; 9 | import org.testng.annotations.Test; 10 | 11 | import static org.testng.Assert.*; 12 | 13 | public class NoMockTest extends BaseWebDriverTest { 14 | 15 | @Test(enabled = false) 16 | @Parameters({"url"}) 17 | public void noMockTest(String url) { 18 | WowXHR wowXHR = wowXHRThreadLocal.get(); 19 | WebDriver driver = wowXHR.getMockDriver(); 20 | DashBoard dashBoard = new DashBoard(driver); 21 | driver.get(url); 22 | dashBoard.waitForLoading(); 23 | 24 | Table userTable = dashBoard.getUserTable(); 25 | assertNotEquals(userTable.getColumnValue("Name", 1), "Sudharsan Selvaraj"); 26 | assertNotEquals(userTable.getColumnValue("Username", 1), "sudharsan"); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /projects/selenium-java/src/test/java/io/github/sudharsan_selvaraj/e2e/admin_panel/tests/RequestMockTests.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.e2e.admin_panel.tests; 2 | 3 | import io.github.sudharsan_selvaraj.e2e.admin_panel.BaseWebDriverTest; 4 | import io.github.sudharsan_selvaraj.e2e.admin_panel.pages.DashBoard; 5 | import io.github.sudharsan_selvaraj.e2e.admin_panel.pages.LoginPage; 6 | import io.github.sudharsan_selvaraj.wowxhr.WowXHR; 7 | import io.github.sudharsan_selvaraj.wowxhr.log.XHRLog; 8 | import io.github.sudharsan_selvaraj.wowxhr.mock.Mockable; 9 | import org.openqa.selenium.By; 10 | import org.openqa.selenium.WebDriver; 11 | import org.openqa.selenium.support.ui.WebDriverWait; 12 | import org.testng.annotations.Parameters; 13 | import org.testng.annotations.Test; 14 | 15 | import java.util.List; 16 | 17 | import static org.testng.Assert.*; 18 | 19 | public class RequestMockTests extends BaseWebDriverTest { 20 | 21 | @Test 22 | @Parameters({"url"}) 23 | public void requestHeadPatchTest(String url) { 24 | WowXHR wowXHR = wowXHRThreadLocal.get(); 25 | WebDriver driver = wowXHR.getMockDriver(); 26 | DashBoard dashBoard = new DashBoard(driver); 27 | 28 | 29 | driver.get(url+ "#/posts/list"); 30 | 31 | wowXHR.mock().add( 32 | Mockable.whenGET("/api/delay") 33 | .modifyRequest( 34 | Mockable.mockRequest() 35 | .setHeader("wow-xhr-token", "invalid") 36 | ) 37 | ); 38 | dashBoard.waitForLoading(); 39 | assertEquals(driver.findElements(By.cssSelector(".humane-flatty-error")).size(), 1); 40 | assertEquals(driver.findElement(By.cssSelector(".humane-flatty-error")).getText(), "State change error:"); 41 | } 42 | 43 | 44 | @Test 45 | @Parameters({"url"}) 46 | public void requestQueryParamTest(String url) { 47 | WowXHR wowXHR = wowXHRThreadLocal.get(); 48 | WebDriver driver = wowXHR.getMockDriver(); 49 | DashBoard dashBoard = new DashBoard(driver); 50 | 51 | wowXHR.mock().add( 52 | Mockable.whenGET("/api/delay/") 53 | .modifyRequest( 54 | Mockable.mockRequest() 55 | .setQueryParam("delay", 1000) 56 | ) 57 | ); 58 | 59 | driver.get(url +"/#/users/list"); 60 | dashBoard.waitForLoading(); 61 | wowXHR.log().clearLogs(); 62 | dashBoard.clickLogo(); 63 | dashBoard.waitForLoading(); 64 | List logs = wowXHR.log().getXHRLogs(); 65 | assertTrue(logs.size() > 0); 66 | logs.stream().filter(l -> l.getRequest().getUrl().contains("api/delay")) 67 | .forEach(l -> assertTrue(l.getRequest().getUrl().contains("delay=1000"))); 68 | } 69 | 70 | @Test 71 | @Parameters({"url"}) 72 | public void requestBodyPatchTest(String url) throws Exception { 73 | WowXHR wowXHR = wowXHRThreadLocal.get(); 74 | WebDriver driver = wowXHR.getMockDriver(); 75 | LoginPage loginPage = new LoginPage(driver); 76 | 77 | Mockable loginMock = Mockable.whenPOST("/api/login") 78 | .modifyRequest( 79 | Mockable.mockRequest() 80 | .setBody("{\"email\":\"invalidemail@emial.com\",\"password\":\"somepassword\"}") 81 | ); 82 | driver.get(url + "/login"); 83 | loginPage.waitForLoading(); 84 | loginPage.login("eve.holt@reqres.in", "cityslicka"); 85 | assertTrue(driver.findElement(By.cssSelector(".login-success")).isDisplayed()); 86 | assertFalse(driver.findElement(By.cssSelector(".invalid-credentials")).isDisplayed()); 87 | 88 | /* Adding mock */ 89 | driver.navigate().refresh(); 90 | wowXHR.mock().add(loginMock); 91 | loginPage.waitForLoading(); 92 | loginPage.login("eve.holt@reqres.in", "cityslicka"); 93 | Thread.sleep(2000); 94 | assertFalse(driver.findElement(By.cssSelector(".login-success")).isDisplayed()); 95 | assertTrue(driver.findElement(By.cssSelector(".invalid-credentials")).isDisplayed()); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /projects/selenium-java/src/test/java/io/github/sudharsan_selvaraj/e2e/admin_panel/tests/ResponseMockTest.java: -------------------------------------------------------------------------------- 1 | package io.github.sudharsan_selvaraj.e2e.admin_panel.tests; 2 | 3 | import io.github.sudharsan_selvaraj.e2e.admin_panel.BaseWebDriverTest; 4 | import io.github.sudharsan_selvaraj.e2e.admin_panel.models.User; 5 | import io.github.sudharsan_selvaraj.e2e.admin_panel.pages.DashBoard; 6 | import io.github.sudharsan_selvaraj.e2e.admin_panel.pages.Table; 7 | import io.github.sudharsan_selvaraj.wowxhr.WowXHR; 8 | import io.github.sudharsan_selvaraj.wowxhr.mock.Mockable; 9 | import org.openqa.selenium.By; 10 | import org.openqa.selenium.WebDriver; 11 | import org.testng.annotations.Parameters; 12 | import org.testng.annotations.Test; 13 | 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | import static org.testng.Assert.*; 18 | 19 | public class ResponseMockTest extends BaseWebDriverTest { 20 | 21 | @Test 22 | @Parameters({"url"}) 23 | public void responseBodyMock(String url) { 24 | WowXHR wowXHR = wowXHRThreadLocal.get(); 25 | WebDriver driver = wowXHR.getMockDriver(); 26 | DashBoard dashBoard = new DashBoard(driver); 27 | 28 | List mockedResponse = Arrays.asList(readListFromJson("/responses/users.json", User[].class)); 29 | 30 | wowXHR.mock().add( 31 | Mockable.whenGET("/api/delay/users") 32 | .respond( 33 | Mockable.mockResponse() 34 | .withBody(mockedResponse) 35 | ) 36 | ); 37 | 38 | driver.get(url + "/#/users/list"); 39 | dashBoard.waitForLoading(); 40 | dashBoard.clickLogo(); 41 | 42 | Table userTable = dashBoard.getUserTable(); 43 | assertEquals(userTable.getColumnValue("Name", 1), "Sudharsan Selvaraj"); 44 | assertEquals(userTable.getColumnValue("Username", 1), "sudharsan"); 45 | } 46 | 47 | @Test 48 | @Parameters({"url"}) 49 | public void responseStatusMock(String url) { 50 | WowXHR wowXHR = wowXHRThreadLocal.get(); 51 | WebDriver driver = wowXHR.getMockDriver(); 52 | DashBoard dashBoard = new DashBoard(driver); 53 | 54 | driver.get(url+"#/users/list"); 55 | wowXHR.mock().add( 56 | Mockable.whenGET("/api/delay") 57 | .respond( 58 | Mockable.mockResponse() 59 | .withStatus(500) 60 | ) 61 | ); 62 | dashBoard.waitForLoading(); 63 | dashBoard.clickPostsMenu(); 64 | assertEquals(driver.findElements(By.cssSelector(".humane-flatty-error")).size(), 1); 65 | assertEquals(driver.findElement(By.cssSelector(".humane-flatty-error")).getText(), "State change error:"); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /projects/selenium-java/src/test/resources/responses/posts_page1.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "body": "Body for post 1", 4 | "id": 1, 5 | "title": "Title for post 1", 6 | "userId": 1 7 | }, 8 | { 9 | "body": "Body for post 2", 10 | "id": 2, 11 | "title": "Title for post 2", 12 | "userId": 2 13 | }, 14 | { 15 | "body": "Body for post 3", 16 | "id": 3, 17 | "title": "Title for post 3", 18 | "userId": 3 19 | }, 20 | { 21 | "body": "Body for post 4", 22 | "id": 4, 23 | "title": "Title for post 4", 24 | "userId": 4 25 | }, 26 | { 27 | "body": "Body for post 5", 28 | "id": 5, 29 | "title": "Title for post 5", 30 | "userId": 5 31 | }, 32 | { 33 | "body": "Body for post 6", 34 | "id": 6, 35 | "title": "Title for post 6", 36 | "userId": 6 37 | }, 38 | { 39 | "body": "Body for post 7", 40 | "id": 7, 41 | "title": "Title for post 7", 42 | "userId": 7 43 | }, 44 | { 45 | "body": "Body for post 8", 46 | "id": 8, 47 | "title": "Title for post 8", 48 | "userId": 8 49 | }, 50 | { 51 | "body": "Body for post 9", 52 | "id": 9, 53 | "title": "Title for post 9", 54 | "userId": 9 55 | }, 56 | { 57 | "body": "Body for post 10", 58 | "id": 10, 59 | "title": "Title for post 10", 60 | "userId": 10 61 | }, 62 | { 63 | "body": "Body for post 11", 64 | "id": 11, 65 | "title": "Title for post 11", 66 | "userId": 1 67 | }, 68 | { 69 | "body": "Body for post 12", 70 | "id": 12, 71 | "title": "Title for post 12", 72 | "userId": 2 73 | }, 74 | { 75 | "body": "Body for post 13", 76 | "id": 13, 77 | "title": "Title for post 13", 78 | "userId": 3 79 | }, 80 | { 81 | "body": "Body for post 14", 82 | "id": 14, 83 | "title": "Title for post 14", 84 | "userId": 4 85 | }, 86 | { 87 | "body": "Body for post 15", 88 | "id": 15, 89 | "title": "Title for post 15", 90 | "userId": 5 91 | }, 92 | { 93 | "body": "Body for post 16", 94 | "id": 16, 95 | "title": "Title for post 16", 96 | "userId": 6 97 | }, 98 | { 99 | "body": "Body for post 17", 100 | "id": 17, 101 | "title": "Title for post 17", 102 | "userId": 7 103 | }, 104 | { 105 | "body": "Body for post 18", 106 | "id": 18, 107 | "title": "Title for post 18", 108 | "userId": 8 109 | }, 110 | { 111 | "body": "Body for post 19", 112 | "id": 19, 113 | "title": "Title for post 19", 114 | "userId": 9 115 | }, 116 | { 117 | "body": "Body for post 20", 118 | "id": 20, 119 | "title": "Title for post 20", 120 | "userId": 10 121 | }, 122 | { 123 | "body": "Body for post 21", 124 | "id": 21, 125 | "title": "Title for post 21", 126 | "userId": 1 127 | }, 128 | { 129 | "body": "Body for post 22", 130 | "id": 22, 131 | "title": "Title for post 22", 132 | "userId": 2 133 | }, 134 | { 135 | "body": "Body for post 23", 136 | "id": 23, 137 | "title": "Title for post 23", 138 | "userId": 3 139 | }, 140 | { 141 | "body": "Body for post 24", 142 | "id": 24, 143 | "title": "Title for post 24", 144 | "userId": 4 145 | }, 146 | { 147 | "body": "Body for post 25", 148 | "id": 25, 149 | "title": "Title for post 25", 150 | "userId": 5 151 | }, 152 | { 153 | "body": "Body for post 26", 154 | "id": 26, 155 | "title": "Title for post 26", 156 | "userId": 6 157 | }, 158 | { 159 | "body": "Body for post 27", 160 | "id": 27, 161 | "title": "Title for post 27", 162 | "userId": 7 163 | }, 164 | { 165 | "body": "Body for post 28", 166 | "id": 28, 167 | "title": "Title for post 28", 168 | "userId": 8 169 | }, 170 | { 171 | "body": "Body for post 29", 172 | "id": 29, 173 | "title": "Title for post 29", 174 | "userId": 9 175 | }, 176 | { 177 | "body": "Body for post 30", 178 | "id": 30, 179 | "title": "Title for post 30", 180 | "userId": 10 181 | } 182 | ] -------------------------------------------------------------------------------- /projects/selenium-java/src/test/resources/responses/posts_page2.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "body": "Body for post 31", 4 | "id": 31, 5 | "title": "Title for post 31", 6 | "userId": 1 7 | }, 8 | { 9 | "body": "Body for post 32", 10 | "id": 32, 11 | "title": "Title for post 32", 12 | "userId": 2 13 | }, 14 | { 15 | "body": "Body for post 33", 16 | "id": 33, 17 | "title": "Title for post 33", 18 | "userId": 3 19 | }, 20 | { 21 | "body": "Body for post 34", 22 | "id": 34, 23 | "title": "Title for post 34", 24 | "userId": 4 25 | }, 26 | { 27 | "body": "Body for post 35", 28 | "id": 35, 29 | "title": "Title for post 35", 30 | "userId": 5 31 | }, 32 | { 33 | "body": "Body for post 36", 34 | "id": 36, 35 | "title": "Title for post 36", 36 | "userId": 6 37 | }, 38 | { 39 | "body": "Body for post 37", 40 | "id": 37, 41 | "title": "Title for post 37", 42 | "userId": 7 43 | }, 44 | { 45 | "body": "Body for post 38", 46 | "id": 38, 47 | "title": "Title for post 38", 48 | "userId": 8 49 | }, 50 | { 51 | "body": "Body for post 39", 52 | "id": 39, 53 | "title": "Title for post 39", 54 | "userId": 9 55 | }, 56 | { 57 | "body": "Body for post 40", 58 | "id": 40, 59 | "title": "Title for post 40", 60 | "userId": 10 61 | }, 62 | { 63 | "body": "Body for post 41", 64 | "id": 41, 65 | "title": "Title for post 41", 66 | "userId": 1 67 | }, 68 | { 69 | "body": "Body for post 42", 70 | "id": 42, 71 | "title": "Title for post 42", 72 | "userId": 2 73 | }, 74 | { 75 | "body": "Body for post 43", 76 | "id": 43, 77 | "title": "Title for post 43", 78 | "userId": 3 79 | }, 80 | { 81 | "body": "Body for post 44", 82 | "id": 44, 83 | "title": "Title for post 44", 84 | "userId": 4 85 | }, 86 | { 87 | "body": "Body for post 45", 88 | "id": 45, 89 | "title": "Title for post 45", 90 | "userId": 5 91 | }, 92 | { 93 | "body": "Body for post 46", 94 | "id": 46, 95 | "title": "Title for post 46", 96 | "userId": 6 97 | }, 98 | { 99 | "body": "Body for post 47", 100 | "id": 47, 101 | "title": "Title for post 47", 102 | "userId": 7 103 | }, 104 | { 105 | "body": "Body for post 48", 106 | "id": 48, 107 | "title": "Title for post 48", 108 | "userId": 8 109 | }, 110 | { 111 | "body": "Body for post 49", 112 | "id": 49, 113 | "title": "Title for post 49", 114 | "userId": 9 115 | }, 116 | { 117 | "body": "Body for post 50", 118 | "id": 50, 119 | "title": "Title for post 50", 120 | "userId": 10 121 | }, 122 | { 123 | "body": "Body for post 51", 124 | "id": 51, 125 | "title": "Title for post 51", 126 | "userId": 1 127 | }, 128 | { 129 | "body": "Body for post 52", 130 | "id": 52, 131 | "title": "Title for post 52", 132 | "userId": 2 133 | }, 134 | { 135 | "body": "Body for post 53", 136 | "id": 53, 137 | "title": "Title for post 53", 138 | "userId": 3 139 | }, 140 | { 141 | "body": "Body for post 54", 142 | "id": 54, 143 | "title": "Title for post 54", 144 | "userId": 4 145 | }, 146 | { 147 | "body": "Body for post 55", 148 | "id": 55, 149 | "title": "Title for post 55", 150 | "userId": 5 151 | }, 152 | { 153 | "body": "Body for post 56", 154 | "id": 56, 155 | "title": "Title for post 56", 156 | "userId": 6 157 | }, 158 | { 159 | "body": "Body for post 57", 160 | "id": 57, 161 | "title": "Title for post 57", 162 | "userId": 7 163 | }, 164 | { 165 | "body": "Body for post 58", 166 | "id": 58, 167 | "title": "Title for post 58", 168 | "userId": 8 169 | }, 170 | { 171 | "body": "Body for post 59", 172 | "id": 59, 173 | "title": "Title for post 59", 174 | "userId": 9 175 | }, 176 | { 177 | "body": "Body for post 60", 178 | "id": 60, 179 | "title": "Title for post 60", 180 | "userId": 10 181 | } 182 | ] -------------------------------------------------------------------------------- /projects/selenium-java/src/test/resources/responses/posts_page3.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "body": "Body for post 61", 4 | "id": 61, 5 | "title": "Title for post 61", 6 | "userId": 1 7 | }, 8 | { 9 | "body": "Body for post 62", 10 | "id": 62, 11 | "title": "Title for post 62", 12 | "userId": 2 13 | }, 14 | { 15 | "body": "Body for post 63", 16 | "id": 63, 17 | "title": "Title for post 63", 18 | "userId": 3 19 | }, 20 | { 21 | "body": "Body for post 64", 22 | "id": 64, 23 | "title": "Title for post 64", 24 | "userId": 4 25 | }, 26 | { 27 | "body": "Body for post 65", 28 | "id": 65, 29 | "title": "Title for post 65", 30 | "userId": 5 31 | }, 32 | { 33 | "body": "Body for post 66", 34 | "id": 66, 35 | "title": "Title for post 66", 36 | "userId": 6 37 | }, 38 | { 39 | "body": "Body for post 67", 40 | "id": 67, 41 | "title": "Title for post 67", 42 | "userId": 7 43 | }, 44 | { 45 | "body": "Body for post 68", 46 | "id": 68, 47 | "title": "Title for post 68", 48 | "userId": 8 49 | }, 50 | { 51 | "body": "Body for post 69", 52 | "id": 69, 53 | "title": "Title for post 69", 54 | "userId": 9 55 | }, 56 | { 57 | "body": "Body for post 70", 58 | "id": 70, 59 | "title": "Title for post 70", 60 | "userId": 10 61 | }, 62 | { 63 | "body": "Body for post 71", 64 | "id": 71, 65 | "title": "Title for post 71", 66 | "userId": 1 67 | }, 68 | { 69 | "body": "Body for post 72", 70 | "id": 72, 71 | "title": "Title for post 72", 72 | "userId": 2 73 | }, 74 | { 75 | "body": "Body for post 73", 76 | "id": 73, 77 | "title": "Title for post 73", 78 | "userId": 3 79 | }, 80 | { 81 | "body": "Body for post 74", 82 | "id": 74, 83 | "title": "Title for post 74", 84 | "userId": 4 85 | }, 86 | { 87 | "body": "Body for post 75", 88 | "id": 75, 89 | "title": "Title for post 75", 90 | "userId": 5 91 | }, 92 | { 93 | "body": "Body for post 76", 94 | "id": 76, 95 | "title": "Title for post 76", 96 | "userId": 6 97 | }, 98 | { 99 | "body": "Body for post 77", 100 | "id": 77, 101 | "title": "Title for post 77", 102 | "userId": 7 103 | }, 104 | { 105 | "body": "Body for post 78", 106 | "id": 78, 107 | "title": "Title for post 78", 108 | "userId": 8 109 | }, 110 | { 111 | "body": "Body for post 79", 112 | "id": 79, 113 | "title": "Title for post 79", 114 | "userId": 9 115 | }, 116 | { 117 | "body": "Body for post 80", 118 | "id": 80, 119 | "title": "Title for post 80", 120 | "userId": 10 121 | }, 122 | { 123 | "body": "Body for post 81", 124 | "id": 81, 125 | "title": "Title for post 81", 126 | "userId": 1 127 | }, 128 | { 129 | "body": "Body for post 82", 130 | "id": 82, 131 | "title": "Title for post 82", 132 | "userId": 2 133 | }, 134 | { 135 | "body": "Body for post 83", 136 | "id": 83, 137 | "title": "Title for post 83", 138 | "userId": 3 139 | }, 140 | { 141 | "body": "Body for post 84", 142 | "id": 84, 143 | "title": "Title for post 84", 144 | "userId": 4 145 | }, 146 | { 147 | "body": "Body for post 85", 148 | "id": 85, 149 | "title": "Title for post 85", 150 | "userId": 5 151 | }, 152 | { 153 | "body": "Body for post 86", 154 | "id": 86, 155 | "title": "Title for post 86", 156 | "userId": 6 157 | }, 158 | { 159 | "body": "Body for post 87", 160 | "id": 87, 161 | "title": "Title for post 87", 162 | "userId": 7 163 | }, 164 | { 165 | "body": "Body for post 88", 166 | "id": 88, 167 | "title": "Title for post 88", 168 | "userId": 8 169 | }, 170 | { 171 | "body": "Body for post 89", 172 | "id": 89, 173 | "title": "Title for post 89", 174 | "userId": 9 175 | }, 176 | { 177 | "body": "Body for post 90", 178 | "id": 90, 179 | "title": "Title for post 90", 180 | "userId": 10 181 | } 182 | ] -------------------------------------------------------------------------------- /projects/selenium-java/src/test/resources/responses/posts_page4.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "body": "Body for post 91", 4 | "id": 91, 5 | "title": "Title for post 91", 6 | "userId": 1 7 | }, 8 | { 9 | "body": "Body for post 92", 10 | "id": 92, 11 | "title": "Title for post 92", 12 | "userId": 2 13 | }, 14 | { 15 | "body": "Body for post 93", 16 | "id": 93, 17 | "title": "Title for post 93", 18 | "userId": 3 19 | }, 20 | { 21 | "body": "Body for post 94", 22 | "id": 94, 23 | "title": "Title for post 94", 24 | "userId": 4 25 | }, 26 | { 27 | "body": "Body for post 95", 28 | "id": 95, 29 | "title": "Title for post 95", 30 | "userId": 5 31 | }, 32 | { 33 | "body": "Body for post 96", 34 | "id": 96, 35 | "title": "Title for post 96", 36 | "userId": 6 37 | }, 38 | { 39 | "body": "Body for post 97", 40 | "id": 97, 41 | "title": "Title for post 97", 42 | "userId": 7 43 | }, 44 | { 45 | "body": "Body for post 98", 46 | "id": 98, 47 | "title": "Title for post 98", 48 | "userId": 8 49 | }, 50 | { 51 | "body": "Body for post 99", 52 | "id": 99, 53 | "title": "Title for post 99", 54 | "userId": 9 55 | }, 56 | { 57 | "body": "Body for post 100", 58 | "id": 100, 59 | "title": "Title for post 100", 60 | "userId": 10 61 | } 62 | ] -------------------------------------------------------------------------------- /projects/selenium-java/src/test/resources/responses/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Sudharsan Selvaraj", 5 | "username": "sudharsan", 6 | "email": "sudharsan@wowxhr.com", 7 | "address": { 8 | "street": "1st street", 9 | "suite": "Apt. 1", 10 | "city": "Tirupur", 11 | "zipcode": "641606", 12 | "geo": { 13 | "lat": "-37.3159", 14 | "lng": "81.1496" 15 | } 16 | }, 17 | "phone": "+911111111111", 18 | "website": "sudharsan.org", 19 | "company": { 20 | "name": "Jumio", 21 | "catchPhrase": "Multi-layered client-server neural-net", 22 | "bs": "harness real-time e-markets" 23 | } 24 | }, 25 | { 26 | "id": 2, 27 | "name": "Sudharsan Selvaraj", 28 | "username": "sudharsan", 29 | "email": "sudharsan@wowxhr.com", 30 | "address": { 31 | "street": "1st street", 32 | "suite": "Apt. 1", 33 | "city": "Tirupur", 34 | "zipcode": "641606", 35 | "geo": { 36 | "lat": "-37.3159", 37 | "lng": "81.1496" 38 | } 39 | }, 40 | "phone": "+911111111111", 41 | "website": "sudharsan.org", 42 | "company": { 43 | "name": "Jumio", 44 | "catchPhrase": "Multi-layered client-server neural-net", 45 | "bs": "harness real-time e-markets" 46 | } 47 | }, 48 | { 49 | "id": 3, 50 | "name": "Sudharsan Selvaraj", 51 | "username": "sudharsan", 52 | "email": "sudharsan@wowxhr.com", 53 | "address": { 54 | "street": "1st street", 55 | "suite": "Apt. 1", 56 | "city": "Tirupur", 57 | "zipcode": "641606", 58 | "geo": { 59 | "lat": "-37.3159", 60 | "lng": "81.1496" 61 | } 62 | }, 63 | "phone": "+911111111111", 64 | "website": "sudharsan.org", 65 | "company": { 66 | "name": "Jumio", 67 | "catchPhrase": "Multi-layered client-server neural-net", 68 | "bs": "harness real-time e-markets" 69 | } 70 | }, 71 | { 72 | "id": 4, 73 | "name": "Sudharsan Selvaraj", 74 | "username": "sudharsan", 75 | "email": "sudharsan@wowxhr.com", 76 | "address": { 77 | "street": "1st street", 78 | "suite": "Apt. 1", 79 | "city": "Tirupur", 80 | "zipcode": "641606", 81 | "geo": { 82 | "lat": "-37.3159", 83 | "lng": "81.1496" 84 | } 85 | }, 86 | "phone": "+911111111111", 87 | "website": "sudharsan.org", 88 | "company": { 89 | "name": "Jumio", 90 | "catchPhrase": "Multi-layered client-server neural-net", 91 | "bs": "harness real-time e-markets" 92 | } 93 | }, 94 | { 95 | "id": 5, 96 | "name": "Sudharsan Selvaraj", 97 | "username": "sudharsan", 98 | "email": "sudharsan@wowxhr.com", 99 | "address": { 100 | "street": "1st street", 101 | "suite": "Apt. 1", 102 | "city": "Tirupur", 103 | "zipcode": "641606", 104 | "geo": { 105 | "lat": "-37.3159", 106 | "lng": "81.1496" 107 | } 108 | }, 109 | "phone": "+911111111111", 110 | "website": "sudharsan.org", 111 | "company": { 112 | "name": "Jumio", 113 | "catchPhrase": "Multi-layered client-server neural-net", 114 | "bs": "harness real-time e-markets" 115 | } 116 | }, 117 | { 118 | "id": 6, 119 | "name": "Sudharsan Selvaraj", 120 | "username": "sudharsan", 121 | "email": "sudharsan@wowxhr.com", 122 | "address": { 123 | "street": "1st street", 124 | "suite": "Apt. 1", 125 | "city": "Tirupur", 126 | "zipcode": "641606", 127 | "geo": { 128 | "lat": "-37.3159", 129 | "lng": "81.1496" 130 | } 131 | }, 132 | "phone": "+911111111111", 133 | "website": "sudharsan.org", 134 | "company": { 135 | "name": "Jumio", 136 | "catchPhrase": "Multi-layered client-server neural-net", 137 | "bs": "harness real-time e-markets" 138 | } 139 | }, 140 | { 141 | "id": 7, 142 | "name": "Sudharsan Selvaraj", 143 | "username": "sudharsan", 144 | "email": "sudharsan@wowxhr.com", 145 | "address": { 146 | "street": "1st street", 147 | "suite": "Apt. 1", 148 | "city": "Tirupur", 149 | "zipcode": "641606", 150 | "geo": { 151 | "lat": "-37.3159", 152 | "lng": "81.1496" 153 | } 154 | }, 155 | "phone": "+911111111111", 156 | "website": "sudharsan.org", 157 | "company": { 158 | "name": "Jumio", 159 | "catchPhrase": "Multi-layered client-server neural-net", 160 | "bs": "harness real-time e-markets" 161 | } 162 | }, 163 | { 164 | "id": 8, 165 | "name": "Sudharsan Selvaraj", 166 | "username": "sudharsan", 167 | "email": "sudharsan@wowxhr.com", 168 | "address": { 169 | "street": "1st street", 170 | "suite": "Apt. 1", 171 | "city": "Tirupur", 172 | "zipcode": "641606", 173 | "geo": { 174 | "lat": "-37.3159", 175 | "lng": "81.1496" 176 | } 177 | }, 178 | "phone": "+911111111111", 179 | "website": "sudharsan.org", 180 | "company": { 181 | "name": "Jumio", 182 | "catchPhrase": "Multi-layered client-server neural-net", 183 | "bs": "harness real-time e-markets" 184 | } 185 | }, 186 | { 187 | "id": 9, 188 | "name": "Sudharsan Selvaraj", 189 | "username": "sudharsan", 190 | "email": "sudharsan@wowxhr.com", 191 | "address": { 192 | "street": "1st street", 193 | "suite": "Apt. 1", 194 | "city": "Tirupur", 195 | "zipcode": "641606", 196 | "geo": { 197 | "lat": "-37.3159", 198 | "lng": "81.1496" 199 | } 200 | }, 201 | "phone": "+911111111111", 202 | "website": "sudharsan.org", 203 | "company": { 204 | "name": "Jumio", 205 | "catchPhrase": "Multi-layered client-server neural-net", 206 | "bs": "harness real-time e-markets" 207 | } 208 | }, 209 | { 210 | "id": 10, 211 | "name": "Sudharsan Selvaraj", 212 | "username": "sudharsan", 213 | "email": "sudharsan@wowxhr.com", 214 | "address": { 215 | "street": "1st street", 216 | "suite": "Apt. 1", 217 | "city": "Tirupur", 218 | "zipcode": "641606", 219 | "geo": { 220 | "lat": "-37.3159", 221 | "lng": "81.1496" 222 | } 223 | }, 224 | "phone": "+911111111111", 225 | "website": "sudharsan.org", 226 | "company": { 227 | "name": "Jumio", 228 | "catchPhrase": "Multi-layered client-server neural-net", 229 | "bs": "harness real-time e-markets" 230 | } 231 | } 232 | ] -------------------------------------------------------------------------------- /projects/selenium-java/src/test/resources/suites/testng.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /wow-xhr-js/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src 3 | webpack.config.js 4 | tsconfig.json -------------------------------------------------------------------------------- /wow-xhr-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wow-xhr-js", 3 | "version": "1.0.1", 4 | "description": "Lightweight utility to mock xhr requests in browser", 5 | "main": "dist/bundle.js", 6 | "scripts": { 7 | "build": "webpack", 8 | "build:watch": "webpack --watch" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "uuid": "^8.3.2" 15 | }, 16 | "devDependencies": { 17 | "expose-loader": "^3.0.0", 18 | "ts-loader": "^9.2.2", 19 | "typescript": "^4.3.2", 20 | "webpack": "^5.38.1", 21 | "webpack-cli": "^4.7.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /wow-xhr-js/src/XhookListener.ts: -------------------------------------------------------------------------------- 1 | import {XhookEvents, XhookRequest, XhookResponse, XhrInterceptor} from "./types"; 2 | import {v4 as uuidv4} from 'uuid'; 3 | 4 | export function initializeXHookListener(xhook: XhookEvents, xhrInterceptors: XhrInterceptor[]) { 5 | xhook.before(async function (request: XhookRequest, callback: () => any) { 6 | request.wow_xhr_id = uuidv4(); 7 | for (var interceptor of xhrInterceptors) { 8 | await interceptor.beforeXHR(request) 9 | } 10 | callback(); 11 | }) 12 | 13 | xhook.after(async function (request: XhookRequest, response: XhookResponse, callback: () => any) { 14 | for (let interceptor of xhrInterceptors) { 15 | await interceptor.afterXHR(request, response) 16 | } 17 | callback(); 18 | }) 19 | } -------------------------------------------------------------------------------- /wow-xhr-js/src/index.ts: -------------------------------------------------------------------------------- 1 | import {XhrLog} from "./modules/XhrLog"; 2 | import {XhrMock} from "./modules/XhrMock"; 3 | import {v4 as uuidv4} from 'uuid'; 4 | import {XhookRequest, XhookResponse} from "./types"; 5 | 6 | require("./modules/xhook.js").xhook; 7 | 8 | var Window: any = window; 9 | 10 | Window.xhook.enable(); 11 | Window.xhrMock = new XhrMock(); 12 | Window.xhrLog = new XhrLog(); 13 | var interceptors = [Window.xhrMock, Window.xhrLog]; 14 | 15 | Window.xhook.before(async function (request: XhookRequest, callback: () => any) { 16 | request.wow_xhr_id = uuidv4(); 17 | for (var interceptor of interceptors) { 18 | await interceptor.beforeXHR(request) 19 | } 20 | callback(); 21 | }) 22 | 23 | Window.xhook.after(async function (request: XhookRequest, response: XhookResponse, callback: () => any) { 24 | for (let interceptor of interceptors) { 25 | await interceptor.afterXHR(request, response) 26 | } 27 | callback(); 28 | }) -------------------------------------------------------------------------------- /wow-xhr-js/src/modules/LogInfo.ts: -------------------------------------------------------------------------------- 1 | import {XhookRequest, XhookResponse} from "../types"; 2 | 3 | class LogInfo { 4 | public initiatedTime: number; 5 | public completedTime: number; 6 | public id: string; 7 | public request: XhookRequest; 8 | public response: XhookResponse; 9 | public pending:boolean; 10 | } 11 | 12 | export { 13 | LogInfo 14 | } -------------------------------------------------------------------------------- /wow-xhr-js/src/modules/XhrLog.ts: -------------------------------------------------------------------------------- 1 | import {XhookRequest, XhookResponse, XhrInterceptor} from "../types"; 2 | import {LogInfo} from "./LogInfo"; 3 | 4 | var LOG_PENDING_CALLS_STORAGE_KEY = "_wow_xhr_log_pending_calls_" 5 | var LOG_COMPLETED_CALLS_STORAGE_KEY = "_wow_xhr_log_completed_calls_" 6 | var WINDOW: any = window; 7 | 8 | class XhrLog implements XhrInterceptor { 9 | constructor() { 10 | try { 11 | [LOG_PENDING_CALLS_STORAGE_KEY, LOG_COMPLETED_CALLS_STORAGE_KEY].forEach(function (key) { 12 | if (!WINDOW.sessionStorage.getItem(key)) { 13 | WINDOW.sessionStorage.setItem(key, "[]"); 14 | } 15 | }) 16 | } catch (err) { 17 | console.log("Unable to set session storage") 18 | } 19 | } 20 | 21 | beforeXHR(request: XhookRequest): Promise { 22 | let xhrId = request.wow_xhr_id; 23 | let logInfo = new LogInfo(); 24 | 25 | logInfo.id = xhrId; 26 | logInfo.request = request; 27 | logInfo.initiatedTime = Date.now(); 28 | logInfo.pending = true; 29 | var pendingCalls = JSON.parse(WINDOW.sessionStorage.getItem(LOG_PENDING_CALLS_STORAGE_KEY) || "[]"); 30 | pendingCalls.push(logInfo) 31 | WINDOW.sessionStorage.setItem(LOG_PENDING_CALLS_STORAGE_KEY, JSON.stringify(pendingCalls)); 32 | return Promise.resolve(); 33 | } 34 | 35 | afterXHR(request: XhookRequest, response: XhookResponse): Promise { 36 | let xhrId = request.wow_xhr_id; 37 | var pendingCalls = JSON.parse(WINDOW.sessionStorage.getItem(LOG_PENDING_CALLS_STORAGE_KEY) || "[]"); 38 | let logIndex = pendingCalls.findIndex(call => call.id == xhrId); 39 | let logInfo: LogInfo = pendingCalls[logIndex]; 40 | logInfo.response = response; 41 | logInfo.completedTime = Date.now(); 42 | logInfo.pending = false; 43 | var completedCalls = JSON.parse(WINDOW.sessionStorage.getItem(LOG_COMPLETED_CALLS_STORAGE_KEY) || "[]"); 44 | completedCalls.push(logInfo); 45 | 46 | this.setCompletedCalls(completedCalls); 47 | this.setPendingCalls(pendingCalls); 48 | return Promise.resolve(undefined); 49 | } 50 | 51 | setCompletedCalls(logs) { 52 | WINDOW.sessionStorage.setItem(LOG_COMPLETED_CALLS_STORAGE_KEY, JSON.stringify(logs)); 53 | } 54 | 55 | setPendingCalls(logs) { 56 | WINDOW.sessionStorage.setItem(LOG_PENDING_CALLS_STORAGE_KEY, JSON.stringify(logs)); 57 | } 58 | } 59 | 60 | export { 61 | XhrLog 62 | } -------------------------------------------------------------------------------- /wow-xhr-js/src/modules/XhrMock.ts: -------------------------------------------------------------------------------- 1 | import { 2 | WowXhrMock, 3 | XhookRequest, 4 | XhookResponse, XhrInterceptor 5 | } from "../types"; 6 | 7 | var MOCK_STORAGE_KEY = "_wow_xhr_mock_" 8 | var MOCK_PAUSED_STORAGE_KEY = "_wow_xhr_mock_paused_" 9 | var WINDOW: any = window; 10 | 11 | class XhrMock implements XhrInterceptor { 12 | 13 | constructor() { 14 | try { 15 | if (!WINDOW.sessionStorage.getItem(MOCK_STORAGE_KEY)) { 16 | WINDOW.sessionStorage.setItem(MOCK_STORAGE_KEY, "[]"); 17 | } 18 | if (!WINDOW.sessionStorage.getItem(MOCK_PAUSED_STORAGE_KEY)) { 19 | WINDOW.sessionStorage.setItem(MOCK_PAUSED_STORAGE_KEY, "false"); 20 | } 21 | } catch (err) { 22 | console.log("Unable set session storage") 23 | } 24 | } 25 | 26 | private findMock(options: { method: string, url: string }): WowXhrMock[] { 27 | 28 | let mocks: WowXhrMock[] = JSON.parse(WINDOW.sessionStorage.getItem(MOCK_STORAGE_KEY) as any || "[]"); 29 | if (WINDOW.sessionStorage.getItem(MOCK_PAUSED_STORAGE_KEY) == "true") { 30 | return; 31 | } 32 | return mocks 33 | .map((m: any) => JSON.parse(m)) 34 | .filter((m: any) => { 35 | return m.method === options.method && 36 | new RegExp(m.url).test(options.url) 37 | }) 38 | } 39 | 40 | beforeXHR(request: XhookRequest): Promise { 41 | let self = this; 42 | return new Promise(function (resolve, reject) { 43 | let {method, url} = request; 44 | let mocks = self.findMock({method, url}); 45 | mocks.forEach(function (mock) { 46 | 47 | if (mock && mock.mockRequest) { 48 | 49 | let {headers, body, queryParams} = mock.mockRequest; 50 | if (headers) { 51 | Object.assign(request.headers, headers) 52 | } 53 | 54 | if (body && new RegExp(method).test("POST|PUT|PATCH")) { 55 | request.body = body; 56 | } 57 | 58 | 59 | if (queryParams && Object.keys(queryParams).length) { 60 | var parsedUrl = new URL(request.url); 61 | parsedUrl.searchParams.forEach(function (val, key) { 62 | if (queryParams.hasOwnProperty(key)) { 63 | parsedUrl.searchParams.set(key, queryParams[key]); 64 | delete queryParams[key]; 65 | } 66 | }) 67 | Object.keys(queryParams).forEach(function (key) { 68 | parsedUrl.searchParams.append(key, queryParams[key]) 69 | }) 70 | request.url = parsedUrl.toString(); 71 | } 72 | } 73 | }) 74 | resolve(); 75 | }) 76 | } 77 | 78 | afterXHR(request: XhookRequest, response: XhookResponse): Promise { 79 | let self = this; 80 | return new Promise(function (resolve, reject) { 81 | let {method, url} = request; 82 | let mocks = self.findMock({method, url}); 83 | let totalDelay = 0; 84 | mocks.forEach(function (mock) { 85 | if (mock && mock.mockResponse) { 86 | let {headers, body, status, delay} = mock.mockResponse; 87 | 88 | if (headers) { 89 | Object.assign(response.headers, headers) 90 | } 91 | 92 | if (body) { 93 | response.text = response.data = body; 94 | } 95 | 96 | if (status) { 97 | response.status = status; 98 | } 99 | 100 | if (delay) { 101 | totalDelay = delay * 1000; 102 | } 103 | } 104 | }) 105 | 106 | setTimeout(resolve, totalDelay); 107 | }) 108 | } 109 | 110 | } 111 | 112 | export { 113 | XhrMock 114 | } -------------------------------------------------------------------------------- /wow-xhr-js/src/modules/xhook.js: -------------------------------------------------------------------------------- 1 | (function (undefined) { 2 | var AFTER, BEFORE, COMMON_EVENTS, EventEmitter, FETCH, FIRE, FormData, NativeFetch, NativeFormData, NativeXMLHttp, 3 | OFF, ON, READY_STATE, UPLOAD_EVENTS, WINDOW, XHookFetchRequest, XHookFormData, XHookHttpRequest, XMLHTTP, 4 | convertHeaders, depricatedProp, document, fakeEvent, mergeObjects, msie, nullify, proxyEvents, slice, useragent, 5 | xhook, _base, 6 | __indexOf = [].indexOf || function (item) { 7 | for (var i = 0, l = this.length; i < l; i++) { 8 | if (i in this && this[i] === item) return i; 9 | } 10 | return -1; 11 | }; 12 | 13 | WINDOW = window; 14 | 15 | document = WINDOW.document; 16 | 17 | BEFORE = 'before'; 18 | 19 | AFTER = 'after'; 20 | 21 | READY_STATE = 'readyState'; 22 | 23 | ON = 'addEventListener'; 24 | 25 | OFF = 'removeEventListener'; 26 | 27 | FIRE = 'dispatchEvent'; 28 | 29 | XMLHTTP = 'XMLHttpRequest'; 30 | 31 | FETCH = 'fetch'; 32 | 33 | FormData = 'FormData'; 34 | 35 | UPLOAD_EVENTS = ['load', 'loadend', 'loadstart']; 36 | 37 | COMMON_EVENTS = ['progress', 'abort', 'error', 'timeout']; 38 | 39 | useragent = typeof navigator !== 'undefined' && navigator['useragent'] ? navigator.userAgent : ''; 40 | 41 | msie = parseInt((/msie (\d+)/.exec(useragent.toLowerCase()) || [])[1]); 42 | 43 | if (isNaN(msie)) { 44 | msie = parseInt((/trident\/.*; rv:(\d+)/.exec(useragent.toLowerCase()) || [])[1]); 45 | } 46 | 47 | (_base = Array.prototype).indexOf || (_base.indexOf = function (item) { 48 | var i, x, _i, _len; 49 | for (i = _i = 0, _len = this.length; _i < _len; i = ++_i) { 50 | x = this[i]; 51 | if (x === item) { 52 | return i; 53 | } 54 | } 55 | return -1; 56 | }); 57 | 58 | slice = function (o, n) { 59 | return Array.prototype.slice.call(o, n); 60 | }; 61 | 62 | depricatedProp = function (p) { 63 | return p === "returnValue" || p === "totalSize" || p === "position"; 64 | }; 65 | 66 | mergeObjects = function (src, dst) { 67 | var k, v; 68 | for (k in src) { 69 | v = src[k]; 70 | if (depricatedProp(k)) { 71 | continue; 72 | } 73 | try { 74 | dst[k] = src[k]; 75 | } catch (_error) { 76 | } 77 | } 78 | return dst; 79 | }; 80 | 81 | nullify = function (res) { 82 | if (res === void 0) { 83 | return null; 84 | } 85 | return res; 86 | }; 87 | 88 | proxyEvents = function (events, src, dst) { 89 | var event, p, _i, _len; 90 | p = function (event) { 91 | return function (e) { 92 | var clone, k, val; 93 | clone = {}; 94 | for (k in e) { 95 | if (depricatedProp(k)) { 96 | continue; 97 | } 98 | val = e[k]; 99 | clone[k] = val === src ? dst : val; 100 | } 101 | return dst[FIRE](event, clone); 102 | }; 103 | }; 104 | for (_i = 0, _len = events.length; _i < _len; _i++) { 105 | event = events[_i]; 106 | if (dst._has(event)) { 107 | src["on" + event] = p(event); 108 | } 109 | } 110 | }; 111 | 112 | fakeEvent = function (type) { 113 | var msieEventObject; 114 | if (document && (document.createEventObject != null)) { 115 | msieEventObject = document.createEventObject(); 116 | msieEventObject.type = type; 117 | return msieEventObject; 118 | } else { 119 | try { 120 | return new Event(type); 121 | } catch (_error) { 122 | return { 123 | type: type 124 | }; 125 | } 126 | } 127 | }; 128 | 129 | EventEmitter = function (nodeStyle) { 130 | var emitter, events, listeners; 131 | events = {}; 132 | listeners = function (event) { 133 | return events[event] || []; 134 | }; 135 | emitter = {}; 136 | emitter[ON] = function (event, callback, i) { 137 | events[event] = listeners(event); 138 | if (events[event].indexOf(callback) >= 0) { 139 | return; 140 | } 141 | i = i === undefined ? events[event].length : i; 142 | events[event].splice(i, 0, callback); 143 | }; 144 | emitter[OFF] = function (event, callback) { 145 | var i; 146 | if (event === undefined) { 147 | events = {}; 148 | return; 149 | } 150 | if (callback === undefined) { 151 | events[event] = []; 152 | } 153 | i = listeners(event).indexOf(callback); 154 | if (i === -1) { 155 | return; 156 | } 157 | listeners(event).splice(i, 1); 158 | }; 159 | emitter[FIRE] = function () { 160 | var args, event, i, legacylistener, listener, _i, _len, _ref; 161 | args = slice(arguments); 162 | event = args.shift(); 163 | if (!nodeStyle) { 164 | args[0] = mergeObjects(args[0], fakeEvent(event)); 165 | } 166 | legacylistener = emitter["on" + event]; 167 | if (legacylistener) { 168 | legacylistener.apply(emitter, args); 169 | } 170 | _ref = listeners(event).concat(listeners("*")); 171 | for (i = _i = 0, _len = _ref.length; _i < _len; i = ++_i) { 172 | listener = _ref[i]; 173 | listener.apply(emitter, args); 174 | } 175 | }; 176 | emitter._has = function (event) { 177 | return !!(events[event] || emitter["on" + event]); 178 | }; 179 | if (nodeStyle) { 180 | emitter.listeners = function (event) { 181 | return slice(listeners(event)); 182 | }; 183 | emitter.on = emitter[ON]; 184 | emitter.off = emitter[OFF]; 185 | emitter.fire = emitter[FIRE]; 186 | emitter.once = function (e, fn) { 187 | var fire; 188 | fire = function () { 189 | emitter.off(e, fire); 190 | return fn.apply(null, arguments); 191 | }; 192 | return emitter.on(e, fire); 193 | }; 194 | emitter.destroy = function () { 195 | return events = {}; 196 | }; 197 | } 198 | return emitter; 199 | }; 200 | 201 | xhook = EventEmitter(true); 202 | 203 | xhook.EventEmitter = EventEmitter; 204 | 205 | xhook[BEFORE] = function (handler, i) { 206 | if (handler.length < 1 || handler.length > 2) { 207 | throw "invalid hook"; 208 | } 209 | return xhook[ON](BEFORE, handler, i); 210 | }; 211 | 212 | xhook[AFTER] = function (handler, i) { 213 | if (handler.length < 2 || handler.length > 3) { 214 | throw "invalid hook"; 215 | } 216 | return xhook[ON](AFTER, handler, i); 217 | }; 218 | 219 | xhook.enable = function () { 220 | WINDOW[XMLHTTP] = XHookHttpRequest; 221 | if (typeof XHookFetchRequest === "function") { 222 | WINDOW[FETCH] = XHookFetchRequest; 223 | } 224 | if (NativeFormData) { 225 | WINDOW[FormData] = XHookFormData; 226 | } 227 | }; 228 | 229 | xhook.disable = function () { 230 | WINDOW[XMLHTTP] = xhook[XMLHTTP]; 231 | WINDOW[FETCH] = xhook[FETCH]; 232 | if (NativeFormData) { 233 | WINDOW[FormData] = NativeFormData; 234 | } 235 | }; 236 | 237 | convertHeaders = xhook.headers = function (h, dest) { 238 | var header, headers, k, name, v, value, _i, _len, _ref; 239 | if (dest == null) { 240 | dest = {}; 241 | } 242 | switch (typeof h) { 243 | case "object": 244 | headers = []; 245 | for (k in h) { 246 | v = h[k]; 247 | name = k.toLowerCase(); 248 | headers.push("" + name + ":\t" + v); 249 | } 250 | return headers.join('\n') + '\n'; 251 | case "string": 252 | headers = h.split('\n'); 253 | for (_i = 0, _len = headers.length; _i < _len; _i++) { 254 | header = headers[_i]; 255 | if (/([^:]+):\s*(.+)/.test(header)) { 256 | name = (_ref = RegExp.$1) != null ? _ref.toLowerCase() : void 0; 257 | value = RegExp.$2; 258 | if (dest[name] == null) { 259 | dest[name] = value; 260 | } 261 | } 262 | } 263 | return dest; 264 | } 265 | }; 266 | 267 | NativeFormData = WINDOW[FormData]; 268 | 269 | XHookFormData = function (form) { 270 | var entries; 271 | this.fd = form ? new NativeFormData(form) : new NativeFormData(); 272 | this.form = form; 273 | entries = []; 274 | Object.defineProperty(this, 'entries', { 275 | get: function () { 276 | var fentries; 277 | fentries = !form ? [] : slice(form.querySelectorAll("input,select")).filter(function (e) { 278 | var _ref; 279 | return ((_ref = e.type) !== 'checkbox' && _ref !== 'radio') || e.checked; 280 | }).map(function (e) { 281 | return [e.name, e.type === "file" ? e.files : e.value]; 282 | }); 283 | return fentries.concat(entries); 284 | } 285 | }); 286 | this.append = (function (_this) { 287 | return function () { 288 | var args; 289 | args = slice(arguments); 290 | entries.push(args); 291 | return _this.fd.append.apply(_this.fd, args); 292 | }; 293 | })(this); 294 | }; 295 | 296 | if (NativeFormData) { 297 | xhook[FormData] = NativeFormData; 298 | WINDOW[FormData] = XHookFormData; 299 | } 300 | 301 | NativeXMLHttp = WINDOW[XMLHTTP]; 302 | 303 | xhook[XMLHTTP] = NativeXMLHttp; 304 | 305 | XHookHttpRequest = WINDOW[XMLHTTP] = function () { 306 | var ABORTED, currentState, emitFinal, emitReadyState, event, facade, hasError, hasErrorHandler, readBody, 307 | readHead, request, response, setReadyState, status, transiting, writeBody, writeHead, xhr, _i, _len, _ref; 308 | ABORTED = -1; 309 | xhr = new xhook[XMLHTTP](); 310 | request = {}; 311 | status = null; 312 | hasError = void 0; 313 | transiting = void 0; 314 | response = void 0; 315 | readHead = function () { 316 | var key, name, val, _ref; 317 | response.status = status || xhr.status; 318 | if (!(status === ABORTED && msie < 10)) { 319 | response.statusText = xhr.statusText; 320 | } 321 | if (status !== ABORTED) { 322 | _ref = convertHeaders(xhr.getAllResponseHeaders()); 323 | for (key in _ref) { 324 | val = _ref[key]; 325 | if (!response.headers[key]) { 326 | name = key.toLowerCase(); 327 | response.headers[name] = val; 328 | } 329 | } 330 | } 331 | }; 332 | readBody = function () { 333 | if (!xhr.responseType || xhr.responseType === "text") { 334 | response.text = xhr.responseText; 335 | response.data = xhr.responseText; 336 | try { 337 | response.xml = xhr.responseXML; 338 | } catch (_error) { 339 | 340 | } 341 | } else if (xhr.responseType === "document") { 342 | response.xml = xhr.responseXML; 343 | response.data = xhr.responseXML; 344 | } else { 345 | response.data = xhr.response; 346 | } 347 | if ("responseURL" in xhr) { 348 | response.finalUrl = xhr.responseURL; 349 | } 350 | }; 351 | writeHead = function () { 352 | facade.status = response.status; 353 | facade.statusText = response.statusText; 354 | }; 355 | writeBody = function () { 356 | if ('text' in response) { 357 | facade.responseText = response.text; 358 | } 359 | if ('xml' in response) { 360 | facade.responseXML = response.xml; 361 | } 362 | if ('data' in response) { 363 | facade.response = response.data; 364 | } 365 | if ('finalUrl' in response) { 366 | facade.responseURL = response.finalUrl; 367 | } 368 | }; 369 | emitReadyState = function (n) { 370 | while (n > currentState && currentState < 4) { 371 | facade[READY_STATE] = ++currentState; 372 | if (currentState === 1) { 373 | facade[FIRE]("loadstart", {}); 374 | } 375 | if (currentState === 2) { 376 | writeHead(); 377 | } 378 | if (currentState === 4) { 379 | writeHead(); 380 | writeBody(); 381 | } 382 | facade[FIRE]("readystatechange", {}); 383 | if (currentState === 4) { 384 | if (request.async === false) { 385 | emitFinal(); 386 | } else { 387 | setTimeout(emitFinal, 0); 388 | } 389 | } 390 | } 391 | }; 392 | emitFinal = function () { 393 | if (!hasError) { 394 | facade[FIRE]("load", {}); 395 | } 396 | facade[FIRE]("loadend", {}); 397 | if (hasError) { 398 | facade[READY_STATE] = 0; 399 | } 400 | }; 401 | currentState = 0; 402 | setReadyState = function (n) { 403 | var hooks, process; 404 | if (n !== 4) { 405 | emitReadyState(n); 406 | return; 407 | } 408 | hooks = xhook.listeners(AFTER); 409 | process = function () { 410 | var hook; 411 | if (!hooks.length) { 412 | return emitReadyState(4); 413 | } 414 | hook = hooks.shift(); 415 | if (hook.length === 2) { 416 | hook(request, response); 417 | return process(); 418 | } else if (hook.length === 3 && request.async) { 419 | return hook(request, response, process); 420 | } else { 421 | return process(); 422 | } 423 | }; 424 | process(); 425 | }; 426 | facade = request.xhr = EventEmitter(); 427 | xhr.onreadystatechange = function (event) { 428 | try { 429 | if (xhr[READY_STATE] === 2) { 430 | readHead(); 431 | } 432 | } catch (_error) { 433 | } 434 | if (xhr[READY_STATE] === 4) { 435 | transiting = false; 436 | readHead(); 437 | readBody(); 438 | } 439 | setReadyState(xhr[READY_STATE]); 440 | }; 441 | hasErrorHandler = function () { 442 | hasError = true; 443 | }; 444 | facade[ON]('error', hasErrorHandler); 445 | facade[ON]('timeout', hasErrorHandler); 446 | facade[ON]('abort', hasErrorHandler); 447 | facade[ON]('progress', function () { 448 | if (currentState < 3) { 449 | setReadyState(3); 450 | } else { 451 | facade[FIRE]("readystatechange", {}); 452 | } 453 | }); 454 | if ('withCredentials' in xhr || xhook.addWithCredentials) { 455 | facade.withCredentials = false; 456 | } 457 | facade.status = 0; 458 | _ref = COMMON_EVENTS.concat(UPLOAD_EVENTS); 459 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 460 | event = _ref[_i]; 461 | facade["on" + event] = null; 462 | } 463 | facade.open = function (method, url, async, user, pass) { 464 | currentState = 0; 465 | hasError = false; 466 | transiting = false; 467 | request.headers = {}; 468 | request.headerNames = {}; 469 | request.status = 0; 470 | response = {}; 471 | response.headers = {}; 472 | request.method = method; 473 | request.url = url; 474 | request.async = async !== false; 475 | request.user = user; 476 | request.pass = pass; 477 | setReadyState(1); 478 | }; 479 | facade.send = function (body) { 480 | var hooks, k, modk, process, send, _j, _len1, _ref1; 481 | _ref1 = ['type', 'timeout', 'withCredentials']; 482 | for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { 483 | k = _ref1[_j]; 484 | modk = k === "type" ? "responseType" : k; 485 | if (modk in facade) { 486 | request[k] = facade[modk]; 487 | } 488 | } 489 | request.body = body; 490 | send = function () { 491 | var header, value, _k, _len2, _ref2, _ref3; 492 | proxyEvents(COMMON_EVENTS, xhr, facade); 493 | if (facade.upload) { 494 | proxyEvents(COMMON_EVENTS.concat(UPLOAD_EVENTS), xhr.upload, facade.upload); 495 | } 496 | transiting = true; 497 | xhr.open(request.method, request.url, request.async, request.user, request.pass); 498 | _ref2 = ['type', 'timeout', 'withCredentials']; 499 | for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { 500 | k = _ref2[_k]; 501 | modk = k === "type" ? "responseType" : k; 502 | if (k in request) { 503 | xhr[modk] = request[k]; 504 | } 505 | } 506 | _ref3 = request.headers; 507 | for (header in _ref3) { 508 | value = _ref3[header]; 509 | if (header) { 510 | xhr.setRequestHeader(header, value); 511 | } 512 | } 513 | if (request.body instanceof XHookFormData) { 514 | request.body = request.body.fd; 515 | } 516 | xhr.send(request.body); 517 | }; 518 | hooks = xhook.listeners(BEFORE); 519 | process = function () { 520 | var done, hook; 521 | if (!hooks.length) { 522 | return send(); 523 | } 524 | done = function (userResponse) { 525 | if (typeof userResponse === 'object' && (typeof userResponse.status === 'number' || typeof response.status === 'number')) { 526 | mergeObjects(userResponse, response); 527 | if (__indexOf.call(userResponse, 'data') < 0) { 528 | userResponse.data = userResponse.response || userResponse.text; 529 | } 530 | setReadyState(4); 531 | return; 532 | } 533 | process(); 534 | }; 535 | done.head = function (userResponse) { 536 | mergeObjects(userResponse, response); 537 | return setReadyState(2); 538 | }; 539 | done.progress = function (userResponse) { 540 | mergeObjects(userResponse, response); 541 | return setReadyState(3); 542 | }; 543 | hook = hooks.shift(); 544 | if (hook.length === 1) { 545 | return done(hook(request)); 546 | } else if (hook.length === 2 && request.async) { 547 | return hook(request, done); 548 | } else { 549 | return done(); 550 | } 551 | }; 552 | process(); 553 | }; 554 | facade.abort = function () { 555 | status = ABORTED; 556 | if (transiting) { 557 | xhr.abort(); 558 | } else { 559 | facade[FIRE]('abort', {}); 560 | } 561 | }; 562 | facade.setRequestHeader = function (header, value) { 563 | var lName, name; 564 | lName = header != null ? header.toLowerCase() : void 0; 565 | name = request.headerNames[lName] = request.headerNames[lName] || header; 566 | if (request.headers[name]) { 567 | value = request.headers[name] + ', ' + value; 568 | } 569 | request.headers[name] = value; 570 | }; 571 | facade.getResponseHeader = function (header) { 572 | var name; 573 | name = header != null ? header.toLowerCase() : void 0; 574 | return nullify(response.headers[name]); 575 | }; 576 | facade.getAllResponseHeaders = function () { 577 | return nullify(convertHeaders(response.headers)); 578 | }; 579 | if (xhr.overrideMimeType) { 580 | facade.overrideMimeType = function () { 581 | return xhr.overrideMimeType.apply(xhr, arguments); 582 | }; 583 | } 584 | if (xhr.upload) { 585 | facade.upload = request.upload = EventEmitter(); 586 | } 587 | facade.UNSENT = 0; 588 | facade.OPENED = 1; 589 | facade.HEADERS_RECEIVED = 2; 590 | facade.LOADING = 3; 591 | facade.DONE = 4; 592 | facade.response = ''; 593 | facade.responseText = ''; 594 | facade.responseXML = null; 595 | facade.readyState = 0; 596 | facade.statusText = ''; 597 | return facade; 598 | }; 599 | 600 | if (typeof WINDOW[FETCH] === "function") { 601 | NativeFetch = WINDOW[FETCH]; 602 | xhook[FETCH] = NativeFetch; 603 | XHookFetchRequest = WINDOW[FETCH] = function (url, options) { 604 | var afterHooks, beforeHooks, request; 605 | if (options == null) { 606 | options = { 607 | headers: {} 608 | }; 609 | } 610 | options.url = url; 611 | request = null; 612 | beforeHooks = xhook.listeners(BEFORE); 613 | afterHooks = xhook.listeners(AFTER); 614 | return new Promise(function (resolve, reject) { 615 | var done, getRequest, processAfter, processBefore, send; 616 | getRequest = function () { 617 | if (options.body instanceof XHookFormData) { 618 | options.body = options.body.fd; 619 | } 620 | if (options.headers) { 621 | options.headers = new Headers(options.headers); 622 | } 623 | if (!request) { 624 | request = new Request(options.url, options); 625 | } 626 | return mergeObjects(options, request); 627 | }; 628 | processAfter = function (response) { 629 | var hook; 630 | if (!afterHooks.length) { 631 | return resolve(response); 632 | } 633 | hook = afterHooks.shift(); 634 | if (hook.length === 2) { 635 | hook(getRequest(), response); 636 | return processAfter(response); 637 | } else if (hook.length === 3) { 638 | return hook(getRequest(), response, processAfter); 639 | } else { 640 | return processAfter(response); 641 | } 642 | }; 643 | done = function (userResponse) { 644 | var response; 645 | if (userResponse !== void 0) { 646 | response = new Response(userResponse.body || userResponse.text, userResponse); 647 | resolve(response); 648 | processAfter(response); 649 | return; 650 | } 651 | processBefore(); 652 | }; 653 | processBefore = function () { 654 | var hook; 655 | if (!beforeHooks.length) { 656 | send(); 657 | return; 658 | } 659 | hook = beforeHooks.shift(); 660 | if (hook.length === 1) { 661 | return done(hook(options)); 662 | } else if (hook.length === 2) { 663 | return hook(getRequest(), done); 664 | } 665 | }; 666 | send = function () { 667 | return NativeFetch(getRequest()).then(function (response) { 668 | return processAfter(response); 669 | })["catch"](function (err) { 670 | processAfter(err); 671 | return reject(err); 672 | }); 673 | }; 674 | processBefore(); 675 | }); 676 | }; 677 | } 678 | 679 | XHookHttpRequest.UNSENT = 0; 680 | 681 | XHookHttpRequest.OPENED = 1; 682 | 683 | XHookHttpRequest.HEADERS_RECEIVED = 2; 684 | 685 | XHookHttpRequest.LOADING = 3; 686 | 687 | XHookHttpRequest.DONE = 4; 688 | 689 | WINDOW.xhook = xhook; 690 | console.log(WINDOW.xhook) 691 | 692 | }.call(this)); -------------------------------------------------------------------------------- /wow-xhr-js/src/types/index.ts: -------------------------------------------------------------------------------- 1 | enum HttpMethod { 2 | GET = "GET", 3 | POST = "POST", 4 | PUT = "PUT", 5 | PATCH = "PATCH", 6 | DELETE = "DELETE", 7 | } 8 | 9 | interface XhookRequest { 10 | wow_xhr_id: string, 11 | method: HttpMethod, 12 | url: string, 13 | body: string, 14 | headers: { [key: string]: string; }, 15 | timeout: number, 16 | type: XMLHttpRequestResponseType, 17 | withCredentials: string 18 | } 19 | 20 | interface XhookResponse { 21 | status: number, 22 | statusText: string, 23 | text: string, 24 | headers: { [key: string]: string; }, 25 | data: string, 26 | } 27 | 28 | interface WowXhrLog { 29 | request: XhookRequest, 30 | response: XhookResponse 31 | } 32 | 33 | type XhookBeforeHandler = (request: XhookRequest, callback?: () => any) => void; 34 | type XhookAfterHandler = (request: XhookRequest, response: XhookResponse, callback?: () => any) => void; 35 | 36 | interface XhookEvents { 37 | before(handler: XhookBeforeHandler): void; 38 | 39 | after(handler: XhookAfterHandler): void; 40 | } 41 | 42 | interface XhrInterceptor { 43 | beforeXHR(request: XhookRequest): Promise; 44 | 45 | afterXHR(request: XhookRequest, response: XhookResponse): Promise; 46 | } 47 | 48 | interface WowXhrMockRequest { 49 | headers: { [key: string]: string; }, 50 | queryParams: { [key: string]: string; }, 51 | body: string 52 | } 53 | 54 | interface WowXhrMockResponse { 55 | headers: { [key: string]: string; }, 56 | body: string, 57 | status: number, 58 | delay: number 59 | } 60 | 61 | interface WowXhrMock { 62 | url: string; 63 | method: HttpMethod, 64 | queryParams?: { [key: string]: string; }, 65 | mockRequest?: WowXhrMockRequest, 66 | mockResponse?: WowXhrMockResponse 67 | } 68 | 69 | export { 70 | HttpMethod, 71 | 72 | XhookRequest, 73 | XhookResponse, 74 | XhookBeforeHandler, 75 | XhookAfterHandler, 76 | XhookEvents, 77 | XhrInterceptor, 78 | 79 | WowXhrMockRequest, 80 | WowXhrMockResponse, 81 | WowXhrLog, 82 | WowXhrMock 83 | } -------------------------------------------------------------------------------- /wow-xhr-js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "system", 4 | "removeComments": true, 5 | "preserveConstEnums": true, 6 | "sourceMap": false, 7 | "moduleResolution": "Node", 8 | "target": "ES5", 9 | "module": "ES2015" 10 | }, 11 | "include": ["src/**/*"], 12 | "exclude": ["node_modules", "**/*.spec.ts"] 13 | } -------------------------------------------------------------------------------- /wow-xhr-js/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | module.exports = { 3 | 4 | mode: 'production', 5 | 6 | entry: './src/index.ts', 7 | 8 | output: { 9 | path: path.resolve(__dirname, 'dist'), 10 | filename: 'bundle.js', 11 | }, 12 | 13 | resolve: { 14 | extensions: ['.ts', '.js'], 15 | }, 16 | 17 | module: { 18 | rules: [ 19 | { 20 | test: /\.tsx?/, 21 | use: { 22 | loader: 'ts-loader', 23 | options: { 24 | transpileOnly: true, 25 | } 26 | }, 27 | exclude: /node_modules/, 28 | }, 29 | { 30 | test: require.resolve('xhook'), 31 | use: [{ 32 | loader: 'expose-loader', 33 | options: { 34 | exposes: { 35 | globalName: 'xhook', 36 | override: true 37 | }, 38 | } 39 | }] 40 | } 41 | ] 42 | } 43 | }; --------------------------------------------------------------------------------