├── 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 |
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 | };
--------------------------------------------------------------------------------