)([\s]*?)(?:)([\s\S]*?)(?:<\/td>)([\s]*?)(?: )([\s\S]*?)(?:)([\s\S]*?)(?:<\/datalist>([\s]*?)<\/td>)([\s]*?)(?:)([\s\S]*?)(?:<\/td>)([\s]*?)(?:<\/tr>)/);
35 |
36 | var new_tr = ' ' + pattern[1] + '' + pattern[2] + '
' + pattern[3] + '' + pattern[4] +
37 | '
\n ' + '' + pattern[5] + ' ' + pattern[6] + ' ' +
38 | pattern[7] + '' + pattern[8] + '
' + pattern[9] + ' ';
39 |
40 | output = output + new_tr + "\n";
41 |
42 | }
43 | output = ' ' + output;
44 | return output;
45 | }
46 |
47 | function readCase(f) {
48 | var grid_content = fileToPanel(f);
49 | if (grid_content) {
50 | clean_panel();
51 | document.getElementById("records-grid").innerHTML = escapeHTML(grid_content);
52 |
53 | var count = getRecordsNum();
54 | if (count !== '0') {
55 | reAssignId("records-1", "records-" + count);
56 | var r = getRecordsArray();
57 | for (var i = 1; i <= count; ++i) {
58 | // do not forget that textNode is a childNode
59 | for (var j = 0; j < 3; ++j) {
60 | var node = document.getElementById("records-" + i).getElementsByTagName("td")[j];
61 | var adjust = unescapeHtml(node.childNodes[0].innerHTML);
62 | node.childNodes[1].appendChild(document.createTextNode(adjust));
63 | }
64 | }
65 | attachEvent(1, count);
66 | }
67 | } else {
68 | clean_panel();
69 | // document.getElementById("records-grid").innerHTML = "";
70 | }
71 |
72 | // append on test grid
73 | var id = "case" + sideex_testCase.count;
74 | sideex_testCase.count++;
75 | var records = document.getElementById("records-grid").innerHTML;
76 | var case_title = f.match(/(?:[\s\S]*?)([\s\S]*?)(?:<\/td>)/)[1];
77 | sideex_testCase[id] = {
78 | records: records,
79 | title: case_title
80 | };
81 | addTestCase(case_title, id);
82 | }
83 |
84 | function readSuite(f) {
85 | var reader = new FileReader();
86 | if (!f.name.includes("htm")) return;
87 | reader.readAsText(f);
88 |
89 | reader.onload = function(event) {
90 | var test_suite = reader.result;
91 | // check for input file version
92 | // if it is not SideeX2, transforming it
93 | if (!checkIsVersion2(test_suite)) {
94 | if (test_suite.search(" 0 && test_suite.search("") < 0) {
95 | // TODO: write a non-blocked confirm window
96 | // confrim user if want to transform input file for loading it
97 | let result = window.confirm("\"" + f.name + "\" is of the format of an early version of Selenium IDE. Some commands may not work. Do you still want to open it?");
98 | if (!result) {
99 | return;
100 | }
101 | // parse for testCase or testSuite
102 | if (checkIsTestSuite(test_suite)) {
103 | // alert("Sorry, we do not support test suite of the format of an early version of Selenium IDE now.");
104 | olderTestSuiteResult = test_suite.substring(0, test_suite.indexOf(""));
105 | olderTestSuiteFile = f;
106 | loadCaseIntoSuite(test_suite);
107 | return;
108 | } else {
109 | test_suite = transformVersion(test_suite);
110 | }
111 | }
112 | // some early version of SideeX2 without
113 | test_suite = addMeta(test_suite);
114 | }
115 |
116 | // append on test grid
117 | appendTestSuite(f, test_suite);
118 | return;
119 | // set up some veraible for recording after loading
120 | };
121 | reader.onerror = function(e) {
122 | console.log("Error", e);
123 | };
124 | }
125 |
126 | document.getElementById("load-testSuite-hidden").addEventListener("change", function(event) {
127 | event.stopPropagation();
128 | for (var i = 0; i < this.files.length; i++) {
129 | readSuite(this.files[i]);
130 | }
131 | this.value = null;
132 | }, false);
133 |
134 | document.getElementById("load-testSuite-show").addEventListener("click", function(event) {
135 | event.stopPropagation();
136 | document.getElementById('load-testSuite-hidden').click();
137 | }, false);
138 |
139 | document.getElementById("load-testSuite-show-menu").addEventListener("click", function(event) {
140 | event.stopPropagation();
141 | document.getElementById('load-testSuite-hidden').click();
142 | }, false);
143 |
144 | $(document).ready(function() {
145 |
146 | $("#testCase-container").on('drag dragstart dragend dragover dragenter dragleave drop', function(e) {
147 | e.preventDefault();
148 | e.stopPropagation();
149 | })
150 | .on('dragover dragenter', function() {
151 | $("#testCase-container").addClass('is-dragover');
152 | })
153 | .on('dragleave dragend drop', function() {
154 | $("#testCase-container").removeClass('is-dragover');
155 | })
156 | .on('drop', function(e) {
157 | let droppedFiles = e.originalEvent.dataTransfer.files;
158 | let droppedFilesLength = droppedFiles.length;
159 | for (var i = 0; i < droppedFilesLength; i++) {
160 | readSuite(droppedFiles[i]);
161 | }
162 | });
163 |
164 | });
--------------------------------------------------------------------------------
/main.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | MeterSphere
10 |
80 |
81 |
82 |
86 |
87 |
88 |
89 |
90 |
92 |
93 |
120 |
121 |
122 |
135 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
--------------------------------------------------------------------------------
/panel/js/UI/panelSetting.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 SideeX committers
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | $(document).ready(function() {
19 |
20 | var userid = browser.runtime.id;
21 | var tac = false;
22 |
23 | /**
24 | * Only used to count the TAC locator usages.
25 | */
26 | browser.storage.sync.get("tac")
27 | .then((res) => {
28 | if (res.tac) {
29 | tac = res.tac;
30 | if (tac) {
31 | $.ajax({
32 | url: 'http://log.sideex.org/usage/tacUsageCount.php',
33 | type: 'POST',
34 | data: {
35 | userid: userid
36 | }
37 | });
38 | }
39 | } else {
40 | browser.storage.sync.set({
41 | tac: tac
42 | });
43 | }
44 | });
45 |
46 | $(".tablesorter").tablesorter();
47 |
48 | $("#help").click(function() {
49 | browser.tabs.create({
50 | url: "http://sideex.org/",
51 | windowId: contentWindowId
52 | });
53 | });
54 |
55 | $("#options").click(function() {
56 | browser.runtime.openOptionsPage();
57 | });
58 |
59 | //init dropdown width
60 | $("#command-dropdown").css({
61 | 'width': $("#command-command").width() + 29 + "px"
62 | });
63 | $("#target-dropdown").css({
64 | 'width': $("#command-target").width() + 29 + "px"
65 | });
66 | //dropdown width change with input's width
67 | $(window).resize(function() {
68 | $("#command-dropdown").css({
69 | 'width': $("#command-command").width() + 29 + "px"
70 | });
71 | $("#target-dropdown").css({
72 | 'width': $("#command-target").width() + 29 + "px"
73 | });
74 | });
75 | //dropdown when click the down icon
76 | $(".fa-chevron-down").click(function() {
77 | dropdown($("#" + $(this).attr("id") + "dropdown"));
78 | $(".w3-show").on("mouseleave", function() {
79 | dropdown($(this));
80 | });
81 | });
82 |
83 | $("#command-grid").colResizable({ liveDrag: true, minWidth: 75 });
84 | $(function() {
85 | $.fn.fixMe = function() {
86 | return this.each(function() {
87 | var $this = $(this),
88 | $t_fixed;
89 |
90 | function init() {
91 | $this.wrap('
');
92 | $t_fixed = $this.clone();
93 | $t_fixed.find("tbody").remove().end().addClass("fixed").insertBefore($this);
94 | $t_fixed.find("th").each(function(index) {
95 | var $self = $(this);
96 | $this.find("th").eq(index).on("DOMAttrModified", function(e) {
97 | $self.css("width", $(this).outerWidth() + "px");
98 | });
99 | });
100 | resizeFixed();
101 | }
102 |
103 | function resizeFixed() {
104 | $t_fixed.find("th").each(function(index) {
105 | $(this).css("width", $this.find("th").eq(index).outerWidth() + "px");
106 | });
107 | }
108 |
109 | function scrollFixed() {
110 | var offset = $(this).scrollTop(),
111 | tableOffsetTop = $this.offset().top,
112 | tableOffsetBottom = tableOffsetTop + $this.height() - $this.find("thead").height();
113 | if (offset < tableOffsetTop || offset > tableOffsetBottom) {
114 | $t_fixed.hide();
115 | } else if (offset >= tableOffsetTop && offset <= tableOffsetBottom && $t_fixed.is(":hidden")) {
116 | $t_fixed.show();
117 | }
118 | var tboffBottom = (parseInt(tableOffsetBottom));
119 | var tboffTop = (parseInt(tableOffsetTop));
120 |
121 | if (offset >= tboffBottom && offset <= tableOffsetBottom) {
122 | $t_fixed.find("th").each(function(index) {
123 | $(this).css("width", $this.find("th").eq(index).outerWidth() + "px");
124 | });
125 | }
126 | }
127 | $(window).resize(resizeFixed);
128 | $(window).scroll(scrollFixed);
129 | init();
130 | });
131 | };
132 | });
133 |
134 | $(".fixed").width($("table:not(.fixed)").width());
135 |
136 | $("#command-dropdown,#command-command-list").html(genCommandDatalist());
137 |
138 | $(".record-bottom").click(function() {
139 | $(this).addClass("active");
140 | $('#records-grid .selectedRecord').removeClass('selectedRecord');
141 | });
142 |
143 | $("#slider").slider({
144 | min: 0,
145 | max: 3000,
146 | value: 0,
147 | step: 600
148 | }).slider("pips", {
149 | rest: "label", labels: ["Fast", "", "", "", "", "Slow"]
150 | });
151 |
152 | });
153 |
154 | var dropdown = function(node) {
155 | if (!node.hasClass("w3-show")) {
156 | node.addClass("w3-show");
157 | setTimeout(function() {
158 | $(document).on("click", clickWhenDropdownHandler);
159 | }, 200);
160 | } else {
161 | $(".w3-show").off("mouseleave");
162 | node.removeClass("w3-show");
163 | $(document).off("click", clickWhenDropdownHandler);
164 | }
165 | };
166 |
167 | var clickWhenDropdownHandler = function(e) {
168 | var event = $(e.target);
169 | if ($(".w3-show").is(event.parent())) {
170 | $(".w3-show").prev().prev().val(event.val()).trigger("input");
171 | }
172 | dropdown($(".w3-show"));
173 | };
174 |
175 | function closeConfirm(bool) {
176 | if (bool) {
177 | $(window).on("beforeunload", function(e) {
178 | var confirmationMessage = "You have a modified suite!";
179 | e.returnValue = confirmationMessage; // Gecko, Trident, Chrome 34+
180 | return confirmationMessage; // Gecko, WebKit, Chrome <34
181 | });
182 | } else {
183 | if (!$("#testCase-grid").find(".modified").length)
184 | $(window).off("beforeunload");
185 | }
186 | }
187 |
188 | function genCommandDatalist() {
189 | var supportedCommand = [
190 | "addSelection",
191 | "answerOnNextPrompt",
192 | "assertAlert",
193 | "assertConfirmation",
194 | "assertPrompt",
195 | "assertText",
196 | "assertTitle",
197 | "assertValue",
198 | "chooseCancelOnNextConfirmation",
199 | "chooseCancelOnNextPrompt",
200 | "chooseOkOnNextConfirmation",
201 | "clickAt",
202 | "close",
203 | "doubleClickAt",
204 | "dragAndDropToObject",
205 | "echo",
206 | "editContent",
207 | "mouseDownAt",
208 | "mouseMoveAt",
209 | "mouseOut",
210 | "mouseOver",
211 | "mouseUpAt",
212 | "open",
213 | "pause",
214 | "removeSelection",
215 | "runScript",
216 | "select",
217 | "selectFrame",
218 | "selectWindow",
219 | "sendKeys",
220 | "store",
221 | "storeEval",
222 | "storeText",
223 | "storeTitle",
224 | "storeValue",
225 | "submit",
226 | "type",
227 | "verifyText",
228 | "verifyTitle",
229 | "verifyValue"
230 | ];
231 |
232 | var datalistHTML = "";
233 | supportedCommand.forEach(function(command) {
234 | datalistHTML += ('' + command + ' \n');
235 | });
236 |
237 | return datalistHTML;
238 | }
--------------------------------------------------------------------------------
/js/main.js:
--------------------------------------------------------------------------------
1 | let downloadRecording = new DownloadRecording();
2 |
3 | $(document).ready(function () {
4 | console.log('Main popup loaded');
5 | chrome.storage.local.get(null, function (item) {
6 | console.log('Storage items:', item);
7 | if (!item.jmxName || !item.traffic || item.traffic.length < 1) {
8 | item.jmxName = generateJmxName();
9 | chrome.storage.local.set({ "jmxName": item.jmxName });
10 | }
11 | $("#jmx_name").val(item.jmxName);
12 |
13 | initOptions(item.options);
14 |
15 | hideBtn('main_download')
16 |
17 | console.log('Sending check_status message...');
18 | let responseReceived = false;
19 | chrome.runtime.sendMessage({ action: "check_status" }, function (response) {
20 | console.log('Check status response:', response);
21 | responseReceived = true;
22 | if (chrome.runtime.lastError) {
23 | console.error('Error checking status:', chrome.runtime.lastError);
24 | // Default to stopped state if there's an error
25 | switchBtn('stopped');
26 | return;
27 | }
28 | let status = response ? response.status : 'stopped';
29 | console.log('Switching to status:', status);
30 | switchBtn(status);
31 | });
32 |
33 | // Add a timeout fallback only if no response is received
34 | setTimeout(function () {
35 | if (!responseReceived) {
36 | console.log('Timeout fallback - no response received from background script');
37 | console.log('Setting default stopped state');
38 | switchBtn('stopped');
39 | } else {
40 | console.log('Response received, skipping timeout fallback');
41 | }
42 | }, 1000);
43 | });
44 | });
45 |
46 | $("#jmx_name").change(() => {
47 | chrome.storage.local.set({ "jmxName": $(" #jmx_name ").val() });
48 | });
49 |
50 | $("input[name='options']").each(function () {
51 | let id = $(this).attr("id");
52 | $(this).change(() => {
53 | chrome.storage.local.get("options", item => {
54 | item.options[id] = $(this).prop('checked');
55 | chrome.storage.local.set({ "options": item.options });
56 | });
57 | });
58 | });
59 |
60 | $('#record_start').click(() => {
61 | switchBtn("recording");
62 | chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
63 | if (tabs.length > 0 && tabs[0].hasOwnProperty('id')) {
64 | let message = {
65 | action: "start_recording",
66 | recordingTab: tabs[0]
67 | };
68 | chrome.runtime.sendMessage(message);
69 | window.close();
70 | }
71 | });
72 | });
73 |
74 | $('#record_pause').click(() => {
75 | switchBtn("pause");
76 | chrome.runtime.sendMessage({ action: "pause_recording" });
77 | });
78 |
79 | $('#record_resume').click(() => {
80 | switchBtn("recording");
81 | chrome.runtime.sendMessage({ action: "resume_recording" });
82 | });
83 |
84 | $('#record_stop').click(() => {
85 | switchBtn("stopped");
86 | chrome.runtime.sendMessage({ action: "stop_recording" });
87 | });
88 |
89 | $('#record_edit').click(() => {
90 | chrome.tabs.create({
91 | url: 'editor.html'
92 | });
93 | });
94 |
95 | $('#record_download').click(() => {
96 | chrome.storage.local.get(['traffic', 'jmxName'], item => {
97 | let name = item.jmxName;
98 | let data = JSON.parse(item.traffic);
99 | let domains = downloadRecording.getDomains(data);
100 | if (domains.length > 1) {
101 | let domainsDiv = $('#checkboxs');
102 | domainsDiv.empty();
103 | domains.forEach(domain => {
104 | domainsDiv.prepend(
105 | '' +
106 | ' ' +
107 | ' ' + domain + ' ' +
108 | '
'
109 | )
110 | });
111 | $('#main_page').hide();
112 | $('#main_download').show();
113 | } else {
114 | downloadRecording.downloadJMX(name, domains, data);
115 | }
116 | });
117 | });
118 |
119 | $('#record_save').click(() => {
120 | let domains = [];
121 | $("input[name='domains']:checked").each(function () {
122 | domains.push($(this).attr("id"));
123 | });
124 | chrome.storage.local.get(['traffic', 'jmxName'], item => {
125 | let name = item.jmxName;
126 | let data = JSON.parse(item.traffic);
127 | downloadRecording.downloadJMX(name, domains, data);
128 | showBtn('main_page');
129 | hideBtn('main_download');
130 | });
131 | });
132 |
133 |
134 | $('#record_back').click(() => {
135 | showBtn('main_page');
136 | hideBtn('main_download');
137 | });
138 |
139 | function switchBtn(status) {
140 | console.log('switchBtn called with status:', status);
141 | switch (status) {
142 | case "recording":
143 | console.log('Setting recording UI');
144 | hideBtns('record_download', 'record_edit', 'record_start', 'record_resume');
145 | showBtns('record_stop', 'record_pause');
146 | break;
147 | case "pause":
148 | console.log('Setting pause UI');
149 | hideBtns('record_download', 'record_edit', 'record_start', 'record_pause');
150 | showBtns('record_stop', 'record_resume');
151 | break;
152 | case "stopped":
153 | console.log('Setting stopped UI');
154 | hideBtns('record_stop', 'record_pause', 'record_resume');
155 | showBtn('record_start');
156 | chrome.storage.local.get('traffic', function (item) {
157 | if (item.traffic && item.traffic.length > 0) {
158 | showBtns('record_download', 'record_edit');
159 | } else {
160 | hideBtns('record_download', 'record_edit');
161 | }
162 | });
163 | break;
164 | }
165 | }
166 |
167 | function showBtn(id) {
168 | if (id.indexOf("#") === -1) {
169 | id = "#" + id;
170 | }
171 | console.log('Showing button:', id);
172 | $(id).show();
173 | }
174 |
175 | function showBtns() {
176 | for (let i = 0; i < arguments.length; i++) {
177 | showBtn(arguments[i]);
178 | }
179 | }
180 |
181 | function hideBtn(id) {
182 | if (id.indexOf("#") === -1) {
183 | id = "#" + id;
184 | }
185 | console.log('Hiding button:', id);
186 | $(id).hide();
187 | }
188 |
189 | function hideBtns() {
190 | for (let i = 0; i < arguments.length; i++) {
191 | hideBtn(arguments[i]);
192 | }
193 | }
194 |
195 | function generateJmxName() {
196 | let d = new Date(),
197 | month = '' + (d.getMonth() + 1),
198 | day = '' + d.getDate(),
199 | year = d.getFullYear(),
200 | hour = d.getHours(),
201 | min = d.getMinutes();
202 |
203 | if (month.length < 2) month = '0' + month;
204 | if (day.length < 2) day = '0' + day;
205 | if (hour.length < 2) hour = '0' + hour;
206 | if (min.length < 2) min = '0' + min;
207 |
208 | return ["RECORD", year, month, day, hour, min].join('-');
209 | }
210 |
211 | function initOptions(options) {
212 | if (!options) {
213 | options = {};
214 | options.requests_to_record = 'top_level';
215 | options.record_ajax = true;
216 | options.functional_test = false;
217 | options.cookie = true;
218 | options.record_css = false;
219 | options.record_js = false;
220 | options.record_images = false;
221 | options.record_other = false;
222 | options.cache = true;
223 | options.regex_include = '';
224 | options.useragent = 'Current Browser';
225 | //options
226 | chrome.storage.local.set({ "options": options });
227 | } else {
228 | if (options.record_ajax) $("#record_ajax").prop("checked", true);
229 | if (options.cookie) $("#cookie").prop("checked", true);
230 | if (options.record_css) $("#record_css").prop("checked", true);
231 | if (options.record_js) $("#record_js").prop("checked", true);
232 | if (options.record_images) $("#record_images").prop("checked", true);
233 | if (options.record_other) $("#record_other").prop("checked", true);
234 | }
235 | }
--------------------------------------------------------------------------------
/common/escape.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 SideeX committers
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | // change HTML entities to sign
19 | function unescapeHtml(str) {
20 | return str
21 | .replace(/&/gi, "&")
22 | .replace(/"/gi, "\"")
23 | .replace(/</gi, "<")
24 | .replace(/>/gi, ">")
25 | .replace(/'/gi, "'");
26 | }
27 |
28 | function escapeAttr(str) {
29 | var spaceS = 0;
30 | var spaceE = -1;
31 | var tempStr = str;
32 | var tempAttr = "";
33 | var tempValue = "";
34 | var processedTag = "";
35 | var flag = false;
36 |
37 | do {
38 | spaceS = str.indexOf(" ");
39 | spaceE = str.indexOf(" ", spaceS + 1);
40 |
41 | if (spaceE >= 0) {
42 | while (str.charAt(spaceE - 1) != "\'" && str.charAt(spaceE - 1) != "\"") {
43 | spaceE = str.indexOf(" ", spaceE + 1);
44 | if (spaceE < 0)
45 | break;
46 | }
47 | }
48 |
49 | //if there is space, then split string
50 | if (spaceS >= 0 && spaceE >= 0) {
51 | tempAttr = str.substring(spaceS + 1, spaceE);
52 | tempStr = str.substring(0, spaceS + 1);
53 | str = str.substring(spaceE);
54 | } else if (spaceS >= 0 && spaceE < 0) {
55 | tempAttr = str.substring(spaceS + 1, str.length - 1);
56 | tempStr = str.substring(0, spaceS + 1);
57 | str = "";
58 | } else {
59 | //flag is check that has string been processed
60 | if (flag)
61 | processedTag += ">";
62 | else
63 | processedTag = str;
64 | break;
65 | }
66 |
67 | flag = true;
68 | var equal = tempAttr.indexOf("=");
69 |
70 | if (tempAttr.charAt(equal + 1) == "\'") {
71 | //divide the single quote
72 | if (tempAttr.indexOf("\'") != -1) {
73 | var quotS = tempAttr.indexOf("\'");
74 | var quotE = tempAttr.lastIndexOf("\'");
75 | tempValue = tempAttr.substring(quotS + 1, quotE);
76 | tempAttr = tempAttr.substring(0, quotS + 1);
77 | tempValue = replaceChar(tempValue);
78 | tempAttr += tempValue + "\'";
79 | }
80 | }
81 | if (tempAttr.charAt(equal + 1) == "\"") {
82 | //divide the double quote
83 | if (tempAttr.indexOf("\"") != -1) {
84 | var dquotS = tempAttr.indexOf("\"");
85 | var dquotE = tempAttr.lastIndexOf("\"");
86 | tempValue = tempAttr.substring(dquotS + 1, dquotE);
87 | tempAttr = tempAttr.substring(0, dquotS + 1);
88 | tempValue = replaceChar(tempValue);
89 | tempAttr += tempValue + "\"";
90 | }
91 | }
92 | //merge the splited string
93 | processedTag += tempStr + tempAttr;
94 | } while (true)
95 |
96 | return processedTag;
97 | };
98 |
99 | //escape the character "<".">"."&"."'".'"'
100 | function doEscape(str) {
101 | return str.replace(/[&"'<>]/g, (m) => ({ "&": "&", '"': """, "'": "'", "<": "<", ">": ">" })[m]);
102 | }
103 |
104 | //append
105 | function checkType(cutStr, replaceStr, mode) {
106 | switch (mode) {
107 | case 1:
108 | return cutStr += replaceStr + "&";
109 | break;
110 | case 2:
111 | return cutStr += replaceStr + """;
112 | break;
113 | case 3:
114 | return cutStr += replaceStr + "'";
115 | break;
116 | case 4:
117 | return cutStr += replaceStr + "<";
118 | break;
119 | case 5:
120 | return cutStr += replaceStr + ">";
121 | break;
122 | default:
123 | return cutStr;
124 | break;
125 | }
126 | }
127 |
128 | //avoid & to escape &
129 | function replaceChar(str) {
130 | //escape the character
131 | var pos = -1;
132 | var cutStr = "";
133 | var replaceStr = "";
134 | var doFlag = 0;
135 | var charType;
136 |
137 | while (true) {
138 | pos = str.indexOf("&", pos + 1);
139 | charType = 0;
140 | if (pos != -1) {
141 | if (str.substring(pos, pos + 5) == "&") {
142 | charType = 1;
143 | replaceStr = str.substring(0, pos);
144 | str = str.substring(pos + 5);
145 | } else if (str.substring(pos, pos + 6) == """) {
146 | charType = 2;
147 | replaceStr = str.substring(0, pos);
148 | str = str.substring(pos + 6);
149 | } else if (str.substring(pos, pos + 5) == "'") {
150 | charType = 3;
151 | replaceStr = str.substring(0, pos);
152 | str = str.substring(pos + 5);
153 | } else if (str.substring(pos, pos + 4) == "<") {
154 | charType = 4;
155 | replaceStr = str.substring(0, pos);
156 | str = str.substring(pos + 4);
157 | } else if (str.substring(pos, pos + 4) == ">") {
158 | charType = 5;
159 | replaceStr = str.substring(0, pos);
160 | str = str.substring(pos + 4);
161 | }
162 |
163 | if (charType != 0) {
164 | //replaceStr = str.substring(0,pos);
165 | //str = str.substring(pos+5);
166 | pos = -1;
167 | //replaceStr = replaceStr.replace(/[&"'<>]/g, (m) => ({ "&": "&", '"': """, "'": "'", "<": "<", ">": ">" })[m]);
168 | replaceStr = doEscape(replaceStr);
169 | //cutStr += replaceStr + "&";
170 | cutStr = checkType(cutStr, replaceStr, charType);
171 | doFlag = 1;
172 | }
173 | } else {
174 | cutStr += str;
175 | break;
176 | }
177 | }
178 | if (doFlag == 0)
179 | //return str.replace(/[&"'<>]/g, (m) => ({ "&": "&", '"': """, "'": "'", "<": "<", ">": ">" })[m]);
180 | return doEscape(str);
181 | else
182 | return cutStr;
183 | }
184 |
185 | //check the HTML value
186 | function escapeHTML(str) {
187 | var smallIndex = str.indexOf("<");
188 | var greatIndex = str.indexOf(">");
189 | var tempStr = "";
190 | var tempTag = "";
191 | var processed = "";
192 | var tempSmallIndex = 0;
193 |
194 | while (true) {
195 | //find the less target
196 | if (smallIndex >= 0) {
197 | //find the greater target
198 | if (greatIndex >= 0) {
199 | do {
200 | //split foreward string
201 | smallIndex += tempSmallIndex;
202 | tempStr = str.substring(0, smallIndex);
203 | //split the tags
204 | tempTag = str.substring(smallIndex, greatIndex + 1);
205 | tempSmallIndex = tempTag.lastIndexOf("<");
206 |
207 | } while (tempSmallIndex != 0)
208 |
209 | //escape attributes in the tag
210 | tempTag = escapeAttr(tempTag);
211 |
212 | str = str.substring(greatIndex + 1);
213 | //check if the tag is script
214 | // if(tempTag.toLowerCase().indexOf("script")>=0)
215 | // tempTag = replaceChar(tempTag);
216 |
217 | //merge them up
218 | processed += replaceChar(tempStr) + tempTag;
219 | } else {
220 | replaceChar(str);
221 | break;
222 | }
223 | } else {
224 | replaceChar(str);
225 | break;
226 | }
227 | //going to do next tag
228 | smallIndex = str.indexOf("<");
229 | greatIndex = 0;
230 | do {
231 | //avoid other >
232 | greatIndex = str.indexOf(">", greatIndex + 1);
233 | } while (greatIndex < smallIndex && greatIndex != -1)
234 | }
235 |
236 | if (str != "")
237 | processed += replaceChar(str);
238 |
239 | return processed;
240 | };
241 |
--------------------------------------------------------------------------------
/panel/js/IO/inputFileTransformer.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 SideeX committers
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 | // coding: utf-8
18 | var olderTestCaseFiles = undefined;
19 | var seleniumBase = undefined;
20 | // for load in testCase
21 | function transformVersion(input) {
22 | getSeleniumBase(input);
23 | let component = splitTbody(input);
24 | component[1] = addDatalistTag(component[1]);
25 | component[0] = addMeta(component[0]);
26 | return component[0] + component[1] + component[2];
27 | }
28 |
29 | function checkIsVersion2(input) {
30 | if (input.search(" ") >= 0) {
31 | return true;
32 | }
33 | return false;
34 | }
35 |
36 | function checkIsTestSuite(input) {
37 | if (input.search("suiteTable") >= 0) {
38 | return true;
39 | }
40 | return false;
41 | }
42 |
43 | // for load in testSuite
44 | function transformTestSuiteVersion(str) {
45 | let component = splitTbody(str);
46 | caseResult = loadCaseIntoSuite(component[1]);
47 | return caseResult;
48 | }
49 |
50 | function loadCaseIntoSuite(str) {
51 | let href = [];
52 | // find what testCase link is in the testSuite
53 | // let anchor = str.match(//g);
54 | let anchor = str.match(/ /g);
55 | for (let i=0 ; i");
109 | let fore = olderTestSuiteResult.substring(0, postindex);
110 | let back = olderTestSuiteResult.substring(postindex);
111 | fore += addDatalistTag(splitTag(str, "table"));
112 |
113 | return fore + back;
114 | }
115 |
116 | // for early version Selenium IDE test case
117 | // because of open command of early version of Selenium IDE test case,
118 | // we need to get its base
119 | function getSeleniumBase(str) {
120 | let bases = str.match(/ " + seleniumBase + str.substring(4, str.length-5) + "";
131 | }
132 |
133 | function appendTestSuite(suiteFile, suiteResult) {
134 | // append on test grid
135 | var id = "suite" + sideex_testSuite.count;
136 | sideex_testSuite.count++;
137 | var suiteFileName;
138 | if (suiteFile.name.lastIndexOf(".") >= 0) {
139 | suiteFileName = suiteFile.name.substring(0, suiteFile.name.lastIndexOf("."));
140 | } else {
141 | suiteFileName = suiteFile.name;
142 | }
143 |
144 | addTestSuite(suiteFileName, id);
145 | // name is used for download
146 | sideex_testSuite[id] = {
147 | file_name: suiteFile.name,
148 | title: suiteFileName
149 | };
150 |
151 | test_case = suiteResult.match(//gi);
152 | if (test_case) {
153 | for (var i = 0; i < test_case.length; ++i) {
154 | readCase(test_case[i]);
155 | }
156 | }
157 |
158 | setSelectedSuite(id);
159 | clean_panel();
160 | }
161 |
162 | function splitTbody(str) {
163 | let preindex = str.indexOf("");
164 | let postindex = str.indexOf(" ");
165 |
166 | let component = [];
167 | component[0] = str.substring(0, preindex);
168 | // NOTE: 8 is "".length
169 | component[1] = str.substring(0, postindex+8).substring(preindex);
170 | component[2] = str.substring(postindex+8);
171 |
172 | return component;
173 | }
174 |
175 | function splitForeAndBack(str, tag) {
176 | let postindex = str.indexOf(tag);
177 | return [str.substring(0, postindex), str.substring(postindex)];
178 | }
179 |
180 | function splitTag(str, tag) {
181 | let preindex = str.indexOf("<" + tag);
182 | let postindex = str.indexOf("" + tag + ">");
183 | // NOTE: 3 is ">".length
184 | return str.substring(preindex, postindex+3+tag.length);
185 | }
186 |
187 | function addDatalistTag(str) {
188 | // for some input with table tag
189 | var tempFore = "";
190 | if (str.search("= 0) {
191 | var tbodyIndex = str.indexOf("");
192 | tempFore = str.substring(0, tbodyIndex);
193 | str = str.substring(tbodyIndex);
194 | }
195 |
196 | let preindex = str.indexOf("");
197 | let postindex = str.indexOf(" ");
198 | let count = 0;
199 | let isOpenCommand = false;
200 | while (preindex>=0 && postindex>=0) {
201 | // check if the command is open, for appending URL base
202 | if (count == 0) {
203 | if (str.substring(preindex, postindex).search("open") >= 0) {
204 | isOpenCommand = true;
205 | }
206 | }
207 |
208 | // NOTE: Because we add datalist tag in second td in every tbody's tr
209 | // we do tjis in evey count equals to 1
210 | if (count == 1) {
211 | // we append Selenium base for open command
212 | if (isOpenCommand) {
213 | let originBase = str.substring(preindex, postindex+5)
214 | let insertBase = appendOpenCommandTarget(originBase);
215 | // NOTE: 5 is "".length
216 | str = str.substring(0, preindex) + insertBase + str.substring(postindex+5);
217 | postindex += (insertBase.length-originBase.length);
218 | isOpenCommand = false;
219 | }
220 |
221 | let insert = "" + addOption(str.substring(preindex, postindex)) + " ";
222 | str = str.substring(0, postindex) + insert + str.substring(postindex);
223 | postindex += insert.length;
224 | }
225 |
226 | preindex = str.indexOf("", preindex+1);
227 | postindex = str.indexOf(" ", postindex+1);
228 | count = (count+1) % 3;
229 | }
230 | return tempFore + str;
231 | }
232 |
233 | function addOption(str) {
234 | return "" + str.substring(4) + " ";
235 | }
236 |
237 | function addMeta(str) {
238 | let part = splitForeAndBack(str, "");
239 | return part[0] + " " + part[1];
240 | }
241 |
242 | var openOldFileDialog = function(question) {
243 | var defer = $.Deferred();
244 | $('
')
245 | .html(question)
246 | .dialog({
247 | title: "Open Test Cases",
248 | resizable: false,
249 | height: "auto",
250 | width: 400,
251 | modal: true,
252 | buttons: {
253 | "browse...": function() {
254 | defer.resolve("true");
255 | $(this).dialog("close");
256 | },
257 | Cancel: function() {
258 | $(this).dialog("close");
259 | }
260 | },
261 | close: function() {
262 | $(this).remove();
263 | }
264 | });
265 | return defer.promise();
266 | };
--------------------------------------------------------------------------------
/panel/css/jquery-ui-slider-pips.css:
--------------------------------------------------------------------------------
1 | /*! jQuery-ui-Slider-Pips - v1.11.4 - 2016-09-04
2 | * Copyright (c) 2016 Simon Goellner ; Licensed MIT */
3 |
4 | /* HORIZONTAL */
5 | /* increase bottom margin to fit the pips */
6 | .ui-slider-horizontal.ui-slider-pips {
7 | margin-bottom: 1.4em;
8 | }
9 |
10 | /* default hide the labels and pips that arnt visible */
11 | /* we just use css to hide incase we want to show certain */
12 | /* labels/pips individually later */
13 | .ui-slider-pips .ui-slider-label,
14 | .ui-slider-pips .ui-slider-pip-hide {
15 | display: none;
16 | }
17 |
18 | /* now we show any labels that we've set to show in the options */
19 | .ui-slider-pips .ui-slider-pip-label .ui-slider-label {
20 | display: block;
21 | }
22 |
23 | /* PIP/LABEL WRAPPER */
24 | /* position each pip absolutely just below the default slider */
25 | /* and also prevent accidental selection */
26 | .ui-slider-pips .ui-slider-pip {
27 | width: 2em;
28 | height: 1em;
29 | line-height: 1em;
30 | position: absolute;
31 | font-size: 0.8em;
32 | color: #999;
33 | overflow: visible;
34 | text-align: center;
35 | top: 20px;
36 | left: 20px;
37 | margin-left: -1em;
38 | cursor: pointer;
39 | -webkit-touch-callout: none;
40 | -webkit-user-select: none;
41 | -moz-user-select: none;
42 | -ms-user-select: none;
43 | user-select: none;
44 | }
45 |
46 | .ui-state-disabled.ui-slider-pips .ui-slider-pip {
47 | cursor: default;
48 | }
49 |
50 | /* little pip/line position & size */
51 | .ui-slider-pips .ui-slider-line {
52 | background: #999;
53 | width: 1px;
54 | height: 3px;
55 | position: absolute;
56 | left: 50%;
57 | }
58 |
59 | /* the text label postion & size */
60 | /* it overflows so no need for width to be accurate */
61 | .ui-slider-pips .ui-slider-label {
62 | position: absolute;
63 | top: 5px;
64 | left: 50%;
65 | margin-left: -1em;
66 | width: 2em;
67 | }
68 |
69 | /* make it easy to see when we hover a label */
70 | .ui-slider-pips:not(.ui-slider-disabled) .ui-slider-pip:hover .ui-slider-label {
71 | color: black;
72 | font-weight: bold;
73 | }
74 |
75 | /* VERTICAL */
76 | /* vertical slider needs right-margin, not bottom */
77 | .ui-slider-vertical.ui-slider-pips {
78 | margin-bottom: 1em;
79 | margin-right: 2em;
80 | }
81 |
82 | /* align vertical pips left and to right of the slider */
83 | .ui-slider-vertical.ui-slider-pips .ui-slider-pip {
84 | text-align: left;
85 | top: auto;
86 | left: 20px;
87 | margin-left: 0;
88 | margin-bottom: -0.5em;
89 | }
90 |
91 | /* vertical line/pip should be horizontal instead */
92 | .ui-slider-vertical.ui-slider-pips .ui-slider-line {
93 | width: 3px;
94 | height: 1px;
95 | position: absolute;
96 | top: 50%;
97 | left: 0;
98 | }
99 |
100 | .ui-slider-vertical.ui-slider-pips .ui-slider-label {
101 | top: 50%;
102 | left: 0.5em;
103 | margin-left: 0;
104 | margin-top: -0.5em;
105 | width: 2em;
106 | }
107 |
108 | /* FLOATING HORIZTONAL TOOLTIPS */
109 | /* remove the godawful looking focus outline on handle and float */
110 | .ui-slider-float .ui-slider-handle:focus,
111 | .ui-slider-float .ui-slider-handle.ui-state-focus .ui-slider-tip-label,
112 | .ui-slider-float .ui-slider-handle:focus .ui-slider-tip,
113 | .ui-slider-float .ui-slider-handle.ui-state-focus .ui-slider-tip-label,
114 | .ui-slider-float .ui-slider-handle:focus .ui-slider-tip-label
115 | .ui-slider-float .ui-slider-handle.ui-state-focus .ui-slider-tip-label {
116 | outline: none;
117 | }
118 |
119 | /* style tooltips on handles and on labels */
120 | /* also has a nice transition */
121 | .ui-slider-float .ui-slider-tip,
122 | .ui-slider-float .ui-slider-tip-label {
123 | position: absolute;
124 | visibility: hidden;
125 | top: -40px;
126 | display: block;
127 | width: 34px;
128 | margin-left: -18px;
129 | left: 50%;
130 | height: 20px;
131 | line-height: 20px;
132 | background: white;
133 | border-radius: 3px;
134 | border: 1px solid #888;
135 | text-align: center;
136 | font-size: 12px;
137 | opacity: 0;
138 | color: #333;
139 | -webkit-transition-property: opacity, top, visibility;
140 | transition-property: opacity, top, visibility;
141 | -webkit-transition-timing-function: ease-in;
142 | transition-timing-function: ease-in;
143 | -webkit-transition-duration: 200ms, 200ms, 0ms;
144 | transition-duration: 200ms, 200ms, 0ms;
145 | -webkit-transition-delay: 0ms, 0ms, 200ms;
146 | transition-delay: 0ms, 0ms, 200ms;
147 | }
148 |
149 | /* show the tooltip on hover or focus */
150 | /* also switch transition delay around */
151 | .ui-slider-float .ui-slider-handle:hover .ui-slider-tip,
152 | .ui-slider-float .ui-slider-handle.ui-state-hover .ui-slider-tip,
153 | .ui-slider-float .ui-slider-handle:focus .ui-slider-tip,
154 | .ui-slider-float .ui-slider-handle.ui-state-focus .ui-slider-tip,
155 | .ui-slider-float .ui-slider-handle.ui-state-active .ui-slider-tip,
156 | .ui-slider-float .ui-slider-pip:hover .ui-slider-tip-label {
157 | opacity: 1;
158 | top: -30px;
159 | visibility: visible;
160 | -webkit-transition-timing-function: ease-out;
161 | transition-timing-function: ease-out;
162 | -webkit-transition-delay: 200ms, 200ms, 0ms;
163 | transition-delay: 200ms, 200ms, 0ms;
164 | }
165 |
166 | /* put label tooltips below slider */
167 | .ui-slider-float .ui-slider-pip .ui-slider-tip-label {
168 | top: 42px;
169 | }
170 |
171 | .ui-slider-float .ui-slider-pip:hover .ui-slider-tip-label {
172 | top: 32px;
173 | font-weight: normal;
174 | }
175 |
176 | /* give the tooltip a css triangle arrow */
177 | .ui-slider-float .ui-slider-tip:after,
178 | .ui-slider-float .ui-slider-pip .ui-slider-tip-label:after {
179 | content: " ";
180 | width: 0;
181 | height: 0;
182 | border: 5px solid rgba(255, 255, 255, 0);
183 | border-top-color: white;
184 | position: absolute;
185 | bottom: -10px;
186 | left: 50%;
187 | margin-left: -5px;
188 | }
189 |
190 | /* put a 1px border on the tooltip arrow to match tooltip border */
191 | .ui-slider-float .ui-slider-tip:before,
192 | .ui-slider-float .ui-slider-pip .ui-slider-tip-label:before {
193 | content: " ";
194 | width: 0;
195 | height: 0;
196 | border: 5px solid rgba(255, 255, 255, 0);
197 | border-top-color: #888;
198 | position: absolute;
199 | bottom: -11px;
200 | left: 50%;
201 | margin-left: -5px;
202 | }
203 |
204 | /* switch the arrow to top on labels */
205 | .ui-slider-float .ui-slider-pip .ui-slider-tip-label:after {
206 | border: 5px solid rgba(255, 255, 255, 0);
207 | border-bottom-color: white;
208 | top: -10px;
209 | }
210 |
211 | .ui-slider-float .ui-slider-pip .ui-slider-tip-label:before {
212 | border: 5px solid rgba(255, 255, 255, 0);
213 | border-bottom-color: #888;
214 | top: -11px;
215 | }
216 |
217 | /* FLOATING VERTICAL TOOLTIPS */
218 | /* tooltip floats to left of handle */
219 | .ui-slider-vertical.ui-slider-float .ui-slider-tip,
220 | .ui-slider-vertical.ui-slider-float .ui-slider-tip-label {
221 | top: 50%;
222 | margin-top: -11px;
223 | width: 34px;
224 | margin-left: 0px;
225 | left: -60px;
226 | color: #333;
227 | -webkit-transition-duration: 200ms, 200ms, 0;
228 | transition-duration: 200ms, 200ms, 0;
229 | -webkit-transition-property: opacity, left, visibility;
230 | transition-property: opacity, left, visibility;
231 | -webkit-transition-delay: 0, 0, 200ms;
232 | transition-delay: 0, 0, 200ms;
233 | }
234 |
235 | .ui-slider-vertical.ui-slider-float .ui-slider-handle:hover .ui-slider-tip,
236 | .ui-slider-vertical.ui-slider-float .ui-slider-handle.ui-state-hover .ui-slider-tip,
237 | .ui-slider-vertical.ui-slider-float .ui-slider-handle:focus .ui-slider-tip,
238 | .ui-slider-vertical.ui-slider-float .ui-slider-handle.ui-state-focus .ui-slider-tip,
239 | .ui-slider-vertical.ui-slider-float .ui-slider-handle.ui-state-active .ui-slider-tip,
240 | .ui-slider-vertical.ui-slider-float .ui-slider-pip:hover .ui-slider-tip-label {
241 | top: 50%;
242 | margin-top: -11px;
243 | left: -50px;
244 | }
245 |
246 | /* put label tooltips to right of slider */
247 | .ui-slider-vertical.ui-slider-float .ui-slider-pip .ui-slider-tip-label {
248 | left: 47px;
249 | }
250 |
251 | .ui-slider-vertical.ui-slider-float .ui-slider-pip:hover .ui-slider-tip-label {
252 | left: 37px;
253 | }
254 |
255 | /* give the tooltip a css triangle arrow */
256 | .ui-slider-vertical.ui-slider-float .ui-slider-tip:after,
257 | .ui-slider-vertical.ui-slider-float .ui-slider-pip .ui-slider-tip-label:after {
258 | border: 5px solid rgba(255, 255, 255, 0);
259 | border-left-color: white;
260 | border-top-color: transparent;
261 | position: absolute;
262 | bottom: 50%;
263 | margin-bottom: -5px;
264 | right: -10px;
265 | margin-left: 0;
266 | top: auto;
267 | left: auto;
268 | }
269 |
270 | .ui-slider-vertical.ui-slider-float .ui-slider-tip:before,
271 | .ui-slider-vertical.ui-slider-float .ui-slider-pip .ui-slider-tip-label:before {
272 | border: 5px solid rgba(255, 255, 255, 0);
273 | border-left-color: #888;
274 | border-top-color: transparent;
275 | position: absolute;
276 | bottom: 50%;
277 | margin-bottom: -5px;
278 | right: -11px;
279 | margin-left: 0;
280 | top: auto;
281 | left: auto;
282 | }
283 |
284 | .ui-slider-vertical.ui-slider-float .ui-slider-pip .ui-slider-tip-label:after {
285 | border: 5px solid rgba(255, 255, 255, 0);
286 | border-right-color: white;
287 | right: auto;
288 | left: -10px;
289 | }
290 |
291 | .ui-slider-vertical.ui-slider-float .ui-slider-pip .ui-slider-tip-label:before {
292 | border: 5px solid rgba(255, 255, 255, 0);
293 | border-right-color: #888;
294 | right: auto;
295 | left: -11px;
296 | }
297 |
298 | /* SELECTED STATES */
299 | /* Comment out this chuck of code if you don't want to have
300 | the new label colours shown */
301 | .ui-slider-pips [class*=ui-slider-pip-initial] {
302 | font-weight: bold;
303 | color: #14CA82;
304 | }
305 |
306 | .ui-slider-pips .ui-slider-pip-initial-2 {
307 | color: #1897C9;
308 | }
309 |
310 | .ui-slider-pips [class*=ui-slider-pip-selected] {
311 | font-weight: bold;
312 | color: #FF7A00;
313 | }
314 |
315 | .ui-slider-pips .ui-slider-pip-inrange {
316 | color: black;
317 | }
318 |
319 | .ui-slider-pips .ui-slider-pip-selected-2 {
320 | color: #E70081;
321 | }
322 |
323 | .ui-slider-pips [class*=ui-slider-pip-selected] .ui-slider-line,
324 | .ui-slider-pips .ui-slider-pip-inrange .ui-slider-line {
325 | background: black;
326 | }
327 |
--------------------------------------------------------------------------------
/page/prompt.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 SideeX committers
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | var originalPrompt = originalPrompt ? originalPrompt : window.prompt;
19 | //var topPrompt = topPrompt ? topPrompt : window.top.prompt;
20 | var nextPromptResult = false;
21 | var recordedPrompt = null;
22 |
23 | var originalConfirmation = originalConfirmation ? originalConfirmation : window.confirm;
24 | var nextConfirmationResult = false;
25 | var recordedConfirmation = null;
26 |
27 | var originalAlert = originalAlert ? originalAlert : window.alert;
28 | var nextAlertResult = false;
29 | var recordedAlert = null;
30 |
31 | function getFrameLocation() {
32 | let frameLocation = "";
33 | let currentWindow = window;
34 | let currentParentWindow;
35 | while (currentWindow !== window.top) {
36 | currentParentWindow = currentWindow.parent;
37 | for (let idx = 0; idx < currentParentWindow.frames.length; idx++)
38 | if (currentParentWindow.frames[idx] === currentWindow) {
39 | frameLocation = ":" + idx + frameLocation;
40 | currentWindow = currentParentWindow;
41 | break;
42 | }
43 | }
44 | frameLocation = "root" + frameLocation;
45 | return frameLocation;
46 | }
47 |
48 |
49 | //before record prompt
50 |
51 | // Not a top window
52 | if (window !== window.top) {
53 |
54 | window.prompt = function(text, defaultText) {
55 | if (document.body.hasAttribute("SideeXPlayingFlag")) {
56 | return window.top.prompt(text, defaultText);
57 | } else {
58 | let result = originalPrompt(text, defaultText);
59 | let frameLocation = getFrameLocation();
60 | window.top.postMessage({
61 | direction: "from-page-script",
62 | recordedType: "prompt",
63 | recordedMessage: text,
64 | recordedResult: result,
65 | frameLocation: frameLocation
66 | }, "*");
67 | return result;
68 | }
69 | };
70 |
71 | window.confirm = function(text) {
72 | if (document.body.hasAttribute("SideeXPlayingFlag")) {
73 | return window.top.confirm(text);
74 | } else {
75 | let result = originalConfirmation(text);
76 | let frameLocation = getFrameLocation();
77 | window.top.postMessage({
78 | direction: "from-page-script",
79 | recordedType: "confirm",
80 | recordedMessage: text,
81 | recordedResult: result,
82 | frameLocation: frameLocation
83 | }, "*");
84 | return result;
85 | }
86 | };
87 |
88 | window.alert = function(text) {
89 | if(document.body.hasAttribute("SideeXPlayingFlag")){
90 | recordedAlert = text;
91 | // Response directly
92 | window.top.postMessage({
93 | direction: "from-page-script",
94 | response: "alert",
95 | value: recordedAlert
96 | }, "*");
97 | return;
98 | } else {
99 | let result = originalAlert(text);
100 | let frameLocation = getFrameLocation();
101 | window.top.postMessage({
102 | direction:"from-page-script",
103 | recordedType: "alert",
104 | recordedMessage: text,
105 | recordedResult: result,
106 | frameLocation: frameLocation
107 | }, "*");
108 | return result;
109 | }
110 | };
111 |
112 | } else { // top window
113 |
114 | window.prompt = function(text, defaultText) {
115 | if (document.body.hasAttribute("setPrompt")) {
116 | recordedPrompt = text;
117 | document.body.removeAttribute("setPrompt");
118 | return nextPromptResult;
119 | } else {
120 | let result = originalPrompt(text, defaultText);
121 | let frameLocation = getFrameLocation();
122 | window.top.postMessage({
123 | direction: "from-page-script",
124 | recordedType: "prompt",
125 | recordedMessage: text,
126 | recordedResult: result,
127 | frameLocation: frameLocation
128 | }, "*");
129 | return result;
130 | }
131 | };
132 | window.confirm = function(text) {
133 | if (document.body.hasAttribute("setConfirm")) {
134 | recordedConfirmation = text;
135 | document.body.removeAttribute("setConfirm");
136 | return nextConfirmationResult;
137 | } else {
138 | let result = originalConfirmation(text);
139 | let frameLocation = getFrameLocation();
140 | window.top.postMessage({
141 | direction: "from-page-script",
142 | recordedType: "confirm",
143 | recordedMessage: text,
144 | recordedResult: result,
145 | frameLocation: frameLocation
146 | }, "*");
147 | return result;
148 | }
149 | };
150 | window.alert = function(text) {
151 | if(document.body.hasAttribute("SideeXPlayingFlag")){
152 | recordedAlert = text;
153 | // Response directly
154 | window.top.postMessage({
155 | direction: "from-page-script",
156 | response: "alert",
157 | value: recordedAlert
158 | }, "*");
159 | return;
160 | } else {
161 | let result = originalAlert(text);
162 | let frameLocation = getFrameLocation();
163 | window.top.postMessage({
164 | direction:"from-page-script",
165 | recordedType: "alert",
166 | recordedMessage: text,
167 | recordedResult: result,
168 | frameLocation: frameLocation
169 | }, "*");
170 | return result;
171 | }
172 | };
173 | }
174 |
175 | //before record confirm
176 | /*
177 | window.confirm = function(text) {
178 | if (document.body.hasAttribute("SideeXPlayingFlag")) {
179 | recordedConfirmation = text;
180 | return nextConfirmationResult;
181 | } else {
182 | let result = originalConfirmation(text);
183 | window.postMessage({
184 | direction: "from-page-script",
185 | recordedType: "confirm",
186 | recordedMessage: text,
187 | recordedResult: result,
188 | }, "*");
189 | return result;
190 | }
191 | };
192 | */
193 | //before record alert
194 | /*
195 | window.alert=function(text){
196 | if(document.body.hasAttribute("SideeXPlayingFlag")){
197 | recordedAlert=text;
198 | return nextAlertResult;
199 | }else{
200 | let result=originalAlert(text);
201 | window.postMessage({
202 | direction:"from-page-script",
203 | recordedType: "alert",
204 | recordedMessage: text,
205 | recordedResult:result,
206 | },"*");
207 | return result;
208 | }
209 | };
210 | */
211 |
212 | //play window methods
213 | if (window == window.top) {
214 | window.addEventListener("message", function(event) {
215 | if (event.source == window && event.data &&
216 | event.data.direction == "from-content-script") {
217 | let result = undefined;
218 | switch (event.data.command) {
219 | case "setNextPromptResult":
220 | nextPromptResult = event.data.target;
221 | document.body.setAttribute("setPrompt", true);
222 | window.postMessage({
223 | direction: "from-page-script",
224 | response: "prompt"
225 | }, "*");
226 | break;
227 | case "getPromptMessage":
228 | result = recordedPrompt;
229 | recordedPrompt = null;
230 | window.postMessage({
231 | direction: "from-page-script",
232 | response: "prompt",
233 | value: result
234 | }, "*");
235 | break;
236 | case "setNextConfirmationResult":
237 | nextConfirmationResult = event.data.target;
238 | document.body.setAttribute("setConfirm", true);
239 | window.postMessage({
240 | direction: "from-page-script",
241 | response: "confirm"
242 | }, "*");
243 | break;
244 | case "getConfirmationMessage":
245 | result = recordedConfirmation;
246 | recordedConfirmation = null;
247 | try{
248 | console.error("no");
249 | window.postMessage({
250 | direction: "from-page-script",
251 | response: "confirm",
252 | value: result
253 | }, "*");
254 | } catch (e) {
255 | console.error(e);
256 | }
257 | break;
258 |
259 | case "setNextAlertResult":
260 | nextAlertResult = event.data.target;
261 | document.body.setAttribute("setAlert", true);
262 | window.postMessage({
263 | direction: "from-page-script",
264 | response: "alert"
265 | }, "*");
266 | break;
267 | // Has been send to content scripts, do not need to require value again
268 | //case "getAlertMessage":
269 | //let result1 = recordedAlert;
270 | //recordedAlert = null;
271 | //window.postMessage({
272 | //direction: "from-page-script",
273 | //response: "alert",
274 | //value: result1
275 | //}, "*");
276 | //break;
277 | }
278 | }
279 | });
280 | }
--------------------------------------------------------------------------------
/js/content-script.js:
--------------------------------------------------------------------------------
1 | const MS_ID = "MeterSphere";
2 |
3 | // Simple draggable implementation to replace jQuery UI
4 | function makeDraggable(element) {
5 | let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
6 | element.style.cursor = 'move';
7 |
8 | element.onmousedown = dragMouseDown;
9 |
10 | function dragMouseDown(e) {
11 | e = e || window.event;
12 | e.preventDefault();
13 | pos3 = e.clientX;
14 | pos4 = e.clientY;
15 | document.onmouseup = closeDragElement;
16 | document.onmousemove = elementDrag;
17 | }
18 |
19 | function elementDrag(e) {
20 | e = e || window.event;
21 | e.preventDefault();
22 | pos1 = pos3 - e.clientX;
23 | pos2 = pos4 - e.clientY;
24 | pos3 = e.clientX;
25 | pos4 = e.clientY;
26 | let newTop = element.offsetTop - pos2;
27 | let newLeft = element.offsetLeft - pos1;
28 |
29 | // Keep within window bounds
30 | if (newTop < 0) newTop = 0;
31 | if (newLeft < 0) newLeft = 0;
32 | if (newTop + element.offsetHeight > window.innerHeight) {
33 | newTop = window.innerHeight - element.offsetHeight;
34 | }
35 | if (newLeft + element.offsetWidth > window.innerWidth) {
36 | newLeft = window.innerWidth - element.offsetWidth;
37 | }
38 |
39 | element.style.top = newTop + "px";
40 | element.style.left = newLeft + "px";
41 | }
42 |
43 | function closeDragElement() {
44 | document.onmouseup = null;
45 | document.onmousemove = null;
46 | // Save position
47 | chrome.storage.local.set({
48 | "position": {
49 | top: element.offsetTop,
50 | left: element.offsetLeft
51 | }
52 | });
53 | }
54 | }
55 |
56 | function injectDivPopup(iframeContainerId, controlsWrapperId, iframeWrapperId) {
57 | if (document.getElementById(iframeContainerId) === null) {
58 | chrome.storage.local.get('position', function (itemsLocal) {
59 | let div = document.createElement('div');
60 | div.style.border = '1px solid #aeaeae';
61 | div.style.borderRadius = '5px';
62 | div.style.boxShadow = '0px 3px 10px #888888';
63 | div.style.position = 'fixed';
64 | div.style.width = '360px';
65 | div.style.height = '230px';
66 | div.style.margin = '0px';
67 | div.style.padding = '0px';
68 | if (itemsLocal.position && Object.keys(itemsLocal.position).length > 0) {
69 | div.style.top = itemsLocal.position.top + "px";
70 | div.style.left = itemsLocal.position.left + "px";
71 | } else {
72 | div.style.top = '0px';
73 | div.style.left = window.innerWidth - 380 + "px";
74 | }
75 |
76 | div.style.zIndex = '9000000000000000000';
77 | div.style.backgroundColor = '#FFF';
78 | div.frameBorder = 'none';
79 | div.setAttribute('id', iframeContainerId);
80 | if (top === self) {
81 | document.documentElement.appendChild(div);
82 | } else {
83 | parent.document.documentElement.appendChild(div);
84 | }
85 | });
86 |
87 | chrome.storage.local.get('theme', function () {
88 | let iframeContainerDiv = document.getElementById(iframeContainerId);
89 | if (iframeContainerDiv) {
90 | iframeContainerDiv.innerHTML =
91 | '' +
92 | '
MeterSphere ' +
93 | '
' +
95 | '
';
96 |
97 | // Add draggable functionality without jQuery
98 | makeDraggable(iframeContainerDiv);
99 | }
100 | });
101 | }
102 | }
103 |
104 | function addTransactionPopupUi() {
105 | console.log('addTransactionPopupUi called');
106 | let iframeId = "iframe-popup-ui-" + MS_ID;
107 | let iframeContainerId = "transaction-popup-ui-" + MS_ID;
108 | let iframeWrapperId = "iframe-wrapper-" + MS_ID;
109 | let controlsWrapperId = "controls-wrapper-" + MS_ID;
110 | let controlsId = "controls-" + MS_ID;
111 | let popup_injected = false;
112 | let inject_iframe = false;
113 | let wait_intervals = -1;
114 | let interval = setInterval(function () {
115 | wait_intervals += 1;
116 | if (document.readyState === 'interactive') {
117 |
118 | if (!popup_injected) {
119 | injectDivPopup(iframeContainerId, controlsWrapperId, iframeWrapperId);
120 | popup_injected = true;
121 | } else {
122 | if (wait_intervals > 100) {
123 | setTimeout(function () {
124 | document.getElementById(iframeWrapperId).style.visibility = "visible";
125 | }, 100);
126 | if (wait_intervals < 200) {
127 | return;
128 | }
129 | }
130 | }
131 | if ((wait_intervals % 10) === 0) {
132 | let iframes = document.getElementsByTagName("iframe");
133 | if (typeof iframes == 'undefined') {
134 | inject_iframe = true;
135 | }
136 | }
137 | }
138 | if (inject_iframe || document.readyState === 'complete') {
139 |
140 | if (!popup_injected) {
141 | injectDivPopup(iframeContainerId, controlsWrapperId, iframeWrapperId);
142 | popup_injected = true;
143 | } else {
144 |
145 | if (document.getElementById(iframeId) === null) {
146 | let iframe = document.createElement('iframe');
147 | iframe.src = chrome.runtime.getURL('html/transaction-ui.html');
148 | //Updated id with GUID
149 | iframe.setAttribute('id', iframeId);
150 | iframe.setAttribute('name', iframeId);
151 |
152 | iframe.style.height = '180px';
153 | iframe.style.width = '358px';
154 | iframe.style.margin = '0';
155 | iframe.style.padding = '0';
156 | iframe.style.overflow = 'hidden';
157 | iframe.style.border = 'none';
158 | iframe.style.position = 'static';
159 |
160 | let controlsIframe = document.createElement('iframe');
161 | controlsIframe.src = chrome.runtime.getURL('html/transaction-controls.html');
162 | //Updated id with GUID
163 | controlsIframe.setAttribute('id', controlsId);
164 | controlsIframe.style.height = '40px';
165 | controlsIframe.style.width = '100px';
166 | controlsIframe.style.margin = '0';
167 | controlsIframe.style.padding = '0';
168 | controlsIframe.style.overflow = 'hidden';
169 | controlsIframe.style.border = 'none';
170 | controlsIframe.style.cssFloat = 'right';
171 | controlsIframe.style.position = 'static';
172 |
173 | let iframeWrapperNode = document.getElementById(iframeWrapperId);
174 | if (iframeWrapperNode !== null) {
175 | while (iframeWrapperNode.firstChild) {
176 | iframeWrapperNode.removeChild(iframeWrapperNode.firstChild);
177 | }
178 |
179 | let iframeControlsWrapperNone = document.getElementById(controlsWrapperId);
180 | if (iframeControlsWrapperNone !== null) {
181 | while (iframeControlsWrapperNone.firstChild) {
182 | iframeControlsWrapperNone.removeChild(iframeControlsWrapperNone.firstChild);
183 | }
184 |
185 | iframeWrapperNode.appendChild(iframe);
186 | iframeControlsWrapperNone.appendChild(controlsIframe);
187 |
188 | setTimeout(function () {
189 | document.getElementById(iframeWrapperId).style.visibility = "visible";
190 | }, 100);
191 | clearInterval(interval);
192 |
193 | }
194 | }
195 | }
196 | }
197 | }
198 |
199 | }, 10);
200 | }
201 |
202 | function removeTransactionPopupUi() {
203 | let iframeContainerId = "transaction-popup-ui-" + MS_ID;
204 | let element = document.getElementById(iframeContainerId);
205 | if (element) {
206 | element.remove();
207 | }
208 | }
209 |
210 | function transactionMessageHandler(request, sender, sendResponse) {
211 | console.log('Content script received message:', request);
212 | switch (request.action) {
213 | case "add_transaction_ui":
214 | console.log('Adding transaction UI');
215 | removeTransactionPopupUi();
216 | addTransactionPopupUi();
217 | sendResponse({ success: true });
218 | return true; // Important for async response
219 | case "remove_transaction_ui":
220 | console.log('Removing transaction UI');
221 | removeTransactionPopupUi();
222 | sendResponse({ success: true });
223 | return true; // Important for async response
224 | }
225 | return false; // Synchronous response for other cases
226 | }
227 |
228 | chrome.runtime.onMessage.addListener(transactionMessageHandler);
229 |
230 | console.log('Content script loaded, checking status...');
231 | // Add a small delay to avoid conflicts with popup initialization
232 | setTimeout(function () {
233 | chrome.runtime.sendMessage({ action: "check_status" }, function (response) {
234 | console.log('Content script status check response:', response);
235 | if (chrome.runtime.lastError) {
236 | console.error('Content script error checking status:', chrome.runtime.lastError);
237 | return;
238 | }
239 | if (response && (response.status === "recording" || response.status === 'pause')) {
240 | console.log('Content script adding UI because status is:', response.status);
241 | addTransactionPopupUi();
242 | }
243 | });
244 | }, 100);
245 |
--------------------------------------------------------------------------------
/panel/js/IO/save_file.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 SideeX committers
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | function saveNewTarget() {
19 | var records = getRecordsArray();
20 | for (var i = 0; i < records.length; ++i) {
21 | var datalist = records[i].getElementsByTagName("datalist")[0];
22 | var options = datalist.getElementsByTagName("option");
23 | var target = getCommandTarget(records[i]);
24 |
25 | if (options.length == 1 && options[0].innerHTML == "") {
26 | options[0].innerHTML = escapeHTML(target);
27 | } else { // check whether it is new target
28 | var new_target = 1;
29 | for (var j = 0; j < options.length; ++j) {
30 | if (unescapeHtml(options[j].innerHTML) == target) {
31 | new_target = 0;
32 | break;
33 | }
34 | }
35 |
36 | if (new_target) {
37 | var new_option = document.createElement("option");
38 | new_option.innerHTML = escapeHTML(target);
39 | datalist.appendChild(new_option);
40 | var x = document.createTextNode("\n ");
41 | datalist.appendChild(x);
42 | }
43 | }
44 | }
45 | }
46 |
47 | function panelToFile(str) {
48 | if (!str) {
49 | return null;
50 | }
51 | str = str.replace(/[\s\S]*?<\/div>/gi, "")
52 | .replace(/
/gi, "")
53 | .replace(/<\/div>/gi, "")
54 | .replace(/
/, "")
55 | .replace(/
/gi, " ");
56 |
57 | var tr = str.match(/ [\s\S]*?<\/tr>/gi);
58 | temp_str = str;
59 | str = "\n";
60 | if (tr)
61 | for (var i = 0; i < tr.length; ++i) {
62 | var pattern = tr[i].match(/([\s]*?)(?:)([\s\S]*?)(?:<\/td>)([\s]*?)(?: )([\s\S]*?)(?:)([\s\S]*?)(?:<\/datalist><\/td>)([\s]*?)(?:)([\s\S]*?)(?:<\/td>)/);
63 | if (!pattern) {
64 | str = temp_str;
65 | break;
66 | }
67 |
68 | var option = pattern[5].match(/[\s\S]*?<\/option>/gi);
69 |
70 | str = str + "" + pattern[1] + "" + pattern[2] + " " + pattern[3] + "" + pattern[4].replace(/\n\s+/g, "") + "";
71 | for (var j = 0; j < option.length; ++j) {
72 | option[j] = option[j].replace(//, "").replace(/<\/option>/, "");
73 | str = str + " " + option[j] + " ";
74 | }
75 | str = str + " " + pattern[6] + "" + pattern[7] + " \n \n";
76 | }
77 | str = '' + str + ' ';
78 | return str;
79 | }
80 |
81 | // var textFile = null,
82 | // makeTextFile = function (text) {
83 | // var data = new Blob([text], {
84 | // type: 'text/html'
85 | // });
86 | // // If we are replacing a previously generated file we need to
87 | // // manually revoke the object URL to avoid memory leaks.
88 | // if (textFile !== null) {
89 | // window.URL.revokeObjectURL(textFile);
90 | // }
91 | // textFile = window.URL.createObjectURL(data);
92 | // return textFile;
93 | // };
94 |
95 | function downloadSuite1(s_suite, callback) {
96 | if (s_suite) {
97 | var cases = s_suite.getElementsByTagName("p"),
98 | output = "",
99 | old_case = getSelectedCase();
100 | for (var i = 0; i < cases.length; ++i) {
101 | setSelectedCase(cases[i].id);
102 | saveNewTarget();
103 | output = output +
104 | '\n\n' +
105 | sideex_testCase[cases[i].id].title +
106 | ' \n \n' +
107 | panelToFile(document.getElementById("records-grid").innerHTML) +
108 | '
\n';
109 | }
110 | output = '\n\n\n\n\t \n\t' +
113 | sideex_testSuite[s_suite.id].title +
114 | ' \n\n\n' +
115 | output +
116 | '\n';
117 |
118 | if (old_case) {
119 | setSelectedCase(old_case.id);
120 | } else {
121 | setSelectedSuite(s_suite.id);
122 | }
123 |
124 | var f_name = sideex_testSuite[s_suite.id].file_name,
125 | link = makeTextFile(output);
126 | var downloading = browser.downloads.download({
127 | filename: f_name,
128 | url: link,
129 | saveAs: true,
130 | conflictAction: 'overwrite'
131 | });
132 |
133 | var result = function (id) {
134 | browser.downloads.onChanged.addListener(function downloadCompleted(downloadDelta) {
135 | if (downloadDelta.id == id && downloadDelta.state &&
136 | downloadDelta.state.current == "complete") {
137 | browser.downloads.search({
138 | id: downloadDelta.id
139 | }).then(function (download) {
140 | download = download[0];
141 | f_name = download.filename.split(/\\|\//).pop();
142 | sideex_testSuite[s_suite.id].file_name = f_name;
143 | sideex_testSuite[s_suite.id].title = f_name.substring(0, f_name.lastIndexOf("."));
144 | $(s_suite).find(".modified").removeClass("modified");
145 | closeConfirm(false);
146 | s_suite.getElementsByTagName("STRONG")[0].textContent = sideex_testSuite[s_suite.id].title;
147 | if (callback) {
148 | callback();
149 | }
150 | browser.downloads.onChanged.removeListener(downloadCompleted);
151 | })
152 | } else if (downloadDelta.id == id && downloadDelta.error) {
153 | browser.downloads.onChanged.removeListener(downloadCompleted);
154 | }
155 | })
156 | };
157 |
158 | var onError = function (error) {
159 | console.log(error);
160 | };
161 |
162 | downloading.then(result, onError);
163 | } else {
164 | alert("Choose a test suite to download!");
165 | }
166 | }
167 |
168 | document.getElementById('save-testSuite').addEventListener('click', function (event) {
169 | event.stopPropagation();
170 | var s_suite = getSelectedSuite();
171 | downloadSuite(s_suite);
172 | }, false);
173 |
174 | function savelog() {
175 | var now = new Date();
176 | var date = now.getDate();
177 | var month = now.getMonth() + 1;
178 | var year = now.getFullYear();
179 | var seconds = now.getSeconds();
180 | var minutes = now.getMinutes();
181 | var hours = now.getHours();
182 | var f_name = year + '-' + month + '-' + date + '-' + hours + '-' + minutes + '-' + seconds + '.log';
183 | var logcontext = "";
184 | var logcontainer = document.getElementById('logcontainer');
185 | for (var i = 0; i < logcontainer.childNodes.length; i++) {
186 | logcontext = logcontext + logcontainer.childNodes[i].textContent + '\n';
187 | }
188 | var link = makeTextFile(logcontext);
189 |
190 | var downloading = browser.downloads.download({
191 | filename: f_name,
192 | url: link,
193 | saveAs: true,
194 | conflictAction: 'overwrite'
195 | });
196 | }
197 |
198 |
199 | function getSuiteJSON(s_suite) {
200 | var cases = s_suite.getElementsByTagName("p"),
201 | output = "",
202 | old_case = getSelectedCase();
203 | for (var i = 0; i < cases.length; ++i) {
204 | setSelectedCase(cases[i].id);
205 | saveNewTarget();
206 | if (i > 0) {
207 | output += ",";
208 | }
209 | output = output +
210 | '{"testStep":"' +
211 | sideex_testCase[cases[i].id].title +
212 | '", \n\t\t "commands": [\n\t\t\t' +
213 | panelToJSON(document.getElementById("records-grid").innerHTML) +
214 | '\n\t\t\t]}\n';
215 | }
216 | output = '{\n\t "suite":"' +
217 | sideex_testSuite[s_suite.id].title +
218 | '",\n\t "test_cases":[\n\t\t' +
219 | output +
220 | ']}';
221 |
222 | if (old_case) {
223 | setSelectedCase(old_case.id);
224 | } else {
225 | setSelectedSuite(s_suite.id);
226 | }
227 | suite_name = sideex_testSuite[s_suite.id].file_name.replace(".html", "");
228 |
229 | return {suite_name: suite_name, output: output};
230 | }
231 |
232 | function downloadSuite(s_suite, callback) {
233 | if (s_suite) {
234 | var suite_json = getSuiteJSON(s_suite);
235 | var f_name = suite_json.suite_name,
236 | //link = makeTextFile(suite_json.output);
237 | link = suite_json.output;
238 |
239 | downloadFile(f_name, link);
240 | } else {
241 | alert("Choose a test suite to download!");
242 | }
243 | }
244 |
245 | function downloadFile(file_name, data) {
246 | let blob = new Blob([data], {type: "application/octet-stream"});
247 | let link = document.createElement('a');
248 | link.href = window.URL.createObjectURL(blob);
249 | link.download = file_name + ".json";
250 | link.click();
251 | window.URL.revokeObjectURL(link.href);
252 | }
253 |
254 |
255 | function panelToJSON(str) {
256 | if (!str) {
257 | return null;
258 | }
259 | str = str.replace(/[\s\S]*?<\/div>/gi, "")
260 | .replace(/
/gi, "")
261 | .replace(/<\/div>/gi, "")
262 | .replace(/ /, "")
263 | .replace(/
/gi, " ");
264 |
265 | var tr = str.match(/ [\s\S]*?<\/tr>/gi);
266 | temp_str = str;
267 | str = "\n";
268 | if (tr)
269 | for (var i = 0; i < tr.length; ++i) {
270 | var pattern = tr[i].match(/([\s]*?)(?:)([\s\S]*?)(?:<\/td>)([\s]*?)(?: )([\s\S]*?)(?:)([\s\S]*?)(?:<\/datalist><\/td>)([\s]*?)(?:)([\s\S]*?)(?:<\/td>)/);
271 | if (!pattern) {
272 | str = temp_str;
273 | break;
274 | }
275 |
276 | var option = pattern[5].match(/[\s\S]*?<\/option>/gi);
277 |
278 | str = str + '\n\t\t\t{"command":"' + pattern[1].trim() + pattern[2].trim() + '", "arg":"' + pattern[3].trim() + '", "target":"' + pattern[4].replace(/\n\s+/g, "").trim().replace("\n", "\\n") + ' ", "datalist":[';
279 | for (var j = 0; j < option.length; ++j) {
280 | option[j] = option[j].replace(/ /, "").replace(/<\/option>/, "");
281 | str = str.trim() + '"' + option[j].trim().replace("\n", "\\n") + '"';
282 | if (j < (option.length - 1)) {
283 | str += ","
284 | }
285 | }
286 | str = str.trim() + "]" + pattern[6].trim() + ' , "value":"' + pattern[7] + '"}';
287 | if (i < (tr.length - 1)) {
288 | str += ","
289 | }
290 | }
291 | str = '' + str + '';
292 | return str;
293 | }
294 |
--------------------------------------------------------------------------------
/panel/js/background/window-controller.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2017 SideeX committers
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | */
17 |
18 | class ExtCommand {
19 |
20 | constructor(contentWindowId) {
21 | this.playingTabNames = {};
22 | this.playingTabIds = {};
23 | this.playingTabStatus = {};
24 | this.playingFrameLocations = {};
25 | this.playingTabCount = 1;
26 | this.currentPlayingTabId = -1;
27 | this.contentWindowId = contentWindowId ? contentWindowId : -1;
28 | this.currentPlayingFrameLocation = 'root';
29 | // TODO: flexible wait
30 | this.waitInterval = 500;
31 | this.waitTimes = 60;
32 |
33 | this.attached = false;
34 |
35 | // Use ES6 arrow function to bind correct this
36 | this.tabsOnUpdatedHandler = (tabId, changeInfo, tabInfo) => {
37 | if (changeInfo.status) {
38 | if (changeInfo.status == "loading") {
39 | this.setLoading(tabId);
40 | } else {
41 | this.setComplete(tabId);
42 | }
43 | }
44 | }
45 |
46 | this.frameLocationMessageHandler = (message, sender) => {
47 | if (message.frameLocation) {
48 | this.setFrame(sender.tab.id, message.frameLocation, sender.frameId);
49 | }
50 | }
51 |
52 | this.newTabHandler = (details) => {
53 | if (this.hasTab(details.sourceTabId)) {
54 | this.setNewTab(details.tabId);
55 | }
56 | }
57 | }
58 |
59 | init() {
60 | this.attach();
61 | this.playingTabNames = {};
62 | this.playingTabIds = {};
63 | this.playingTabStatus = {};
64 | this.playingFrameLocations = {};
65 | this.playingTabCount = 1;
66 | this.currentPlayingWindowId = this.contentWindowId;
67 | let self = this;
68 | this.currentPlayingFrameLocation = "root";
69 | return this.queryActiveTab(this.currentPlayingWindowId)
70 | .then(this.setFirstTab.bind(this));
71 | }
72 |
73 | clear() {
74 | this.detach();
75 | this.playingTabNames = {};
76 | this.playingTabIds = {};
77 | this.playingTabStatus = {};
78 | this.playingFrameLocations = {};
79 | this.playingTabCount = 1;
80 | this.currentPlayingWindowId = undefined;
81 | }
82 |
83 | attach() {
84 | if(this.attached) {
85 | return;
86 | }
87 | this.attached = true;
88 | browser.tabs.onUpdated.addListener(this.tabsOnUpdatedHandler);
89 | browser.runtime.onMessage.addListener(this.frameLocationMessageHandler);
90 | browser.webNavigation.onCreatedNavigationTarget.addListener(this.newTabHandler);
91 | }
92 |
93 | detach() {
94 | if(!this.attached) {
95 | return;
96 | }
97 | this.attached = false;
98 | browser.tabs.onUpdated.removeListener(this.tabsOnUpdatedHandler);
99 | browser.runtime.onMessage.removeListener(this.frameLocationMessageHandler);
100 | browser.webNavigation.onCreatedNavigationTarget.removeListener(this.newTabHandler);
101 | }
102 |
103 | setContentWindowId(contentWindowId) {
104 | this.contentWindowId = contentWindowId;
105 | }
106 |
107 | getContentWindowId() {
108 | return this.contentWindowId;
109 | }
110 |
111 | getCurrentPlayingTabId() {
112 | return this.currentPlayingTabId;
113 | }
114 |
115 | getCurrentPlayingFrameLocation() {
116 | return this.currentPlayingFrameLocation;
117 | }
118 |
119 | getFrameId(tabId) {
120 | if (tabId >= 0) {
121 | return this.playingFrameLocations[tabId][this.currentPlayingFrameLocation];
122 | } else {
123 | return this.playingFrameLocations[this.currentPlayingTabId][this.currentPlayingFrameLocation];
124 | }
125 | }
126 |
127 | getCurrentPlayingFrameId() {
128 | return this.getFrameId(this.currentPlayingTabId);
129 | }
130 |
131 | getPageStatus() {
132 | return this.playingTabStatus[this.getCurrentPlayingTabId()];
133 | }
134 |
135 | queryActiveTab(windowId) {
136 | return browser.tabs.query({windowId: windowId, active: true, url: ["http://*/*", "https://*/*"]})
137 | .then(function(tabs) {
138 | return tabs[0];
139 | });
140 | }
141 |
142 | sendCommand(command, target, value, top) {
143 | let tabId = this.getCurrentPlayingTabId();
144 | let frameId = this.getCurrentPlayingFrameId();
145 | return browser.tabs.sendMessage(tabId, {
146 | commands: command,
147 | target: target,
148 | value: value
149 | }, { frameId: top ? 0 : frameId });
150 | }
151 |
152 | setLoading(tabId) {
153 | // Does clearing the object will cause some problem(e.g. missing the frameId)?
154 | // Ans: Yes, but I don't know why
155 | this.initTabInfo(tabId);
156 | // this.initTabInfo(tabId, true); (failed)
157 | this.playingTabStatus[tabId] = false;
158 | }
159 |
160 | setComplete(tabId) {
161 | this.initTabInfo(tabId);
162 | this.playingTabStatus[tabId] = true;
163 | }
164 |
165 | initTabInfo(tabId, forced) {
166 | if (!this.playingFrameLocations[tabId] | forced) {
167 | this.playingFrameLocations[tabId] = {};
168 | this.playingFrameLocations[tabId]["root"] = 0;
169 | }
170 | }
171 |
172 | setFrame(tabId, frameLocation, frameId) {
173 | this.playingFrameLocations[tabId][frameLocation] = frameId;
174 | }
175 |
176 | hasTab(tabId) {
177 | return this.playingTabIds[tabId];
178 | }
179 |
180 | setNewTab(tabId) {
181 | this.playingTabNames["win_ser_" + this.playingTabCount] = tabId;
182 | this.playingTabIds[tabId] = "win_ser_" + this.playingTabCount;
183 | this.playingTabCount++;
184 | }
185 |
186 | doOpen(url) {
187 | return browser.tabs.update(this.currentPlayingTabId, {
188 | url: url
189 | })
190 | }
191 |
192 | doPause(ignored, milliseconds) {
193 | return new Promise(function(resolve) {
194 | setTimeout(resolve, milliseconds);
195 | });
196 | }
197 |
198 | doSelectFrame(frameLocation) {
199 | let result = frameLocation.match(/(index|relative) *= *([\d]+|parent)/i);
200 | if (result && result[2]) {
201 | let position = result[2];
202 | if (position == "parent") {
203 | this.currentPlayingFrameLocation = this.currentPlayingFrameLocation.slice(0, this.currentPlayingFrameLocation.lastIndexOf(':'));
204 | } else {
205 | this.currentPlayingFrameLocation += ":" + position;
206 | }
207 | return this.wait("playingFrameLocations", this.currentPlayingTabId, this.currentPlayingFrameLocation);
208 | } else {
209 | return Promise.reject("Invalid argument");
210 | }
211 | }
212 |
213 | doSelectWindow(serialNumber) {
214 | let self = this;
215 | return this.wait("playingTabNames", serialNumber)
216 | .then(function() {
217 | self.currentPlayingTabId = self.playingTabNames[serialNumber];
218 | browser.tabs.update(self.currentPlayingTabId, {active: true});
219 | })
220 | }
221 |
222 | doClose() {
223 | let removingTabId = this.currentPlayingTabId;
224 | this.currentPlayingTabId = -1;
225 | delete this.playingFrameLocations[removingTabId];
226 | return browser.tabs.remove(removingTabId);
227 | }
228 |
229 | wait(...properties) {
230 | if (!properties.length)
231 | return Promise.reject("No arguments");
232 | let self = this;
233 | let ref = this;
234 | let inspecting = properties[properties.length - 1];
235 | for (let i = 0; i < properties.length - 1; i++) {
236 | if (!ref[properties[i]] | !(ref[properties[i]] instanceof Array | ref[properties[i]] instanceof Object))
237 | return Promise.reject("Invalid Argument");
238 | ref = ref[properties[i]];
239 | }
240 | return new Promise(function(resolve, reject) {
241 | let counter = 0;
242 | let interval = setInterval(function() {
243 | if (ref[inspecting] === undefined || ref[inspecting] === false) {
244 | counter++;
245 | if (counter > self.waitTimes) {
246 | reject("Timeout");
247 | clearInterval(interval);
248 | }
249 | } else {
250 | resolve();
251 | clearInterval(interval);
252 | }
253 | }, self.waitInterval);
254 | })
255 | }
256 |
257 | updateOrCreateTab() {
258 | let self = this;
259 | return browser.tabs.query({
260 | windowId: self.currentPlayingWindowId,
261 | active: true
262 | }).then(function(tabs) {
263 | if (tabs.length === 0) {
264 | return browser.windows.create({
265 | url: "https://google.com"
266 | }).then(function (window) {
267 | self.setFirstTab(window.tabs[0]);
268 | self.contentWindowId = window.id;
269 | recorder.setOpenedWindow(window.id);
270 | browser.runtime.getBackgroundPage()
271 | .then(function(backgroundWindow) {
272 | backgroundWindow.master[window.id] = recorder.getSelfWindowId();
273 | });
274 | })
275 | } else {
276 | let tabInfo = null;
277 | return browser.tabs.update(tabs[0].id, {
278 | url: "https://google.com"
279 | }).then(function(tab) {
280 | tabInfo = tab;
281 | return self.wait("playingTabStatus", tab.id);
282 | }).then(function() {
283 | // Firefox did not update url information when tab is updated
284 | // We assign url manually and go to set first tab
285 | tabInfo.url = "https://google.com";
286 | self.setFirstTab(tabInfo);
287 | })
288 | }
289 | })
290 | }
291 |
292 | setFirstTab(tab) {
293 | if (!tab || (tab.url && this.isAddOnPage(tab.url))) {
294 | return this.updateOrCreateTab()
295 | } else {
296 | this.currentPlayingTabId = tab.id;
297 | this.playingTabNames["win_ser_local"] = this.currentPlayingTabId;
298 | this.playingTabIds[this.currentPlayingTabId] = "win_ser_local";
299 | this.playingFrameLocations[this.currentPlayingTabId] = {};
300 | this.playingFrameLocations[this.currentPlayingTabId]["root"] = 0;
301 | // we assume that there has an "open" command
302 | // select Frame directly will cause failed
303 | this.playingTabStatus[this.currentPlayingTabId] = true;
304 | }
305 | }
306 |
307 | isAddOnPage(url) {
308 | if (url.startsWith("https://addons.mozilla.org") ||
309 | url.startsWith("https://chrome.google.com/webstore")) {
310 | return true;
311 | }
312 | return false;
313 | }
314 | }
315 |
316 | function isExtCommand(command) {
317 | switch(command) {
318 | case "pause":
319 | //case "open":
320 | case "selectFrame":
321 | case "selectWindow":
322 | case "close":
323 | return true;
324 | default:
325 | return false;
326 | }
327 | }
328 |
--------------------------------------------------------------------------------
/js/transaction-ui.js:
--------------------------------------------------------------------------------
1 | let transactions = null;
2 | let tabKeyPressed = false;
3 |
4 | // 从service worker获取transactions数据
5 | async function getTransactionsFromBackground() {
6 | return new Promise((resolve) => {
7 | chrome.runtime.sendMessage({ action: 'get_transactions' }, (response) => {
8 | if (chrome.runtime.lastError) {
9 | console.error('Error getting transactions:', chrome.runtime.lastError);
10 | resolve(null);
11 | } else {
12 | resolve(response ? response.transactions : null);
13 | }
14 | });
15 | });
16 | }
17 |
18 | $(document).ready(async function () {
19 | // 初始化时获取transactions数据
20 | try {
21 | transactions = await getTransactionsFromBackground();
22 | if (!transactions) {
23 | // 如果获取失败,创建一个空的transactions对象
24 | transactions = {
25 | httpTransactions: [],
26 | addHttpTransaction: function (name) {
27 | chrome.runtime.sendMessage({ action: 'add_transaction', name: name });
28 | },
29 | setHttpTransactionName: function (index, name) {
30 | chrome.runtime.sendMessage({ action: 'set_transaction_name', index: index, name: name });
31 | },
32 | getLastHttpTransactionCounter: function () {
33 | return this.httpTransactions.length > 0 ? this.httpTransactions[this.httpTransactions.length - 1].counter : 0;
34 | },
35 | getLastHttpTransaction: function () {
36 | return this.httpTransactions.length > 0 ? this.httpTransactions[this.httpTransactions.length - 1] : { counter: 0 };
37 | }
38 | };
39 | }
40 | } catch (error) {
41 | console.error('Failed to get transactions from background:', error);
42 | transactions = {
43 | httpTransactions: [],
44 | addHttpTransaction: function (name) {
45 | chrome.runtime.sendMessage({ action: 'add_transaction', name: name });
46 | },
47 | setHttpTransactionName: function (index, name) {
48 | chrome.runtime.sendMessage({ action: 'set_transaction_name', index: index, name: name });
49 | },
50 | getLastHttpTransactionCounter: function () {
51 | return this.httpTransactions.length > 0 ? this.httpTransactions[this.httpTransactions.length - 1].counter : 0;
52 | },
53 | getLastHttpTransaction: function () {
54 | return this.httpTransactions.length > 0 ? this.httpTransactions[this.httpTransactions.length - 1] : { counter: 0 };
55 | }
56 | };
57 | }
58 |
59 | // 初始渲染
60 | renderTransactions();
61 | let nameInput = $('#transaction_name');
62 | let addBtn = $('#add-transaction');
63 | let listUl = $('#transactions');
64 |
65 | nameInput.keyup(function (event) {
66 | if (!$(this).val()) {
67 | addBtn.addClass('disabled').attr('disabled', true);
68 | return;
69 | } else {
70 | addBtn.removeClass('disabled').removeAttr('disabled');
71 | }
72 | if (event.keyCode === 13) {
73 | // 回车 添加
74 | addNewTransaction($(this).val());
75 | $(this).val('');
76 | addBtn.addClass('disabled');
77 | $('#transaction-list').scrollTop(listUl[0].scrollHeight);
78 | } else if (event.keyCode === 27) {
79 | //Esc 取消
80 | $(this).val('');
81 | addBtn.addClass('disabled');
82 | }
83 | });
84 |
85 | // 不再硬编码placeholder,将由toggleAddTransaction动态设置
86 |
87 | nameInput.keypress(function (event) {
88 | if (event.which === '13') {
89 | event.preventDefault();
90 | }
91 | });
92 |
93 | $(document).on('click', '.transaction-name', function () {
94 | $(this).hide();
95 | $(this).next().show();
96 | $(this).next().focus();
97 | $(this).next().val($(this).text());
98 | });
99 |
100 | $(document).on('keydown', '.transaction-input', function (event) {
101 | // 是否允许Tab键
102 | tabKeyPressed = event.keyCode === 9;
103 | if (tabKeyPressed) {
104 | event.preventDefault();
105 | }
106 | });
107 |
108 | $(document).on('focusout', '.transaction-input', function () {
109 | $(this).hide();
110 | $(this).prev().show();
111 | $(this).prev().focus();
112 | let key = $(this).data('key');
113 | let transactionName = $(this).val();
114 | if (!stringIsEmpty(transactionName)) {
115 | $(this).prev().text(transactionName);
116 | // 通过消息传递更新transaction名称
117 | chrome.runtime.sendMessage({
118 | action: 'set_transaction_name',
119 | index: key,
120 | name: transactionName
121 | }, () => {
122 | // 本地也更新
123 | if (transactions.httpTransactions[key]) {
124 | transactions.httpTransactions[key].name = transactionName;
125 | }
126 | chrome.runtime.sendMessage({ action: 'update_transactions' });
127 | });
128 | }
129 | tabKeyPressed = false;
130 | });
131 |
132 | $('.transaction-input').focusout(function () {
133 | $(this).hide();
134 | $(this).prev().show();
135 | $(this).prev().focus();
136 | });
137 |
138 | addBtn.click(function () {
139 | let transactionName = nameInput.val();
140 | addNewTransaction(transactionName);
141 | nameInput.val('');
142 | addBtn.addClass('disabled');
143 | });
144 |
145 | function isMacOrIOS() {
146 | let platform = navigator.platform;
147 | let isMac = platform.indexOf('Mac') > -1;
148 | let iosPlatforms = ['iPhone', 'iPad', 'iPod'];
149 | let isIos = iosPlatforms.indexOf(platform) !== -1;
150 | return isMac || isIos;
151 | }
152 |
153 | function addMacClass() {
154 | if (isMacOrIOS()) {
155 | $('#transaction-content').addClass('mac');
156 | }
157 | }
158 |
159 | function liType(httpTransaction) {
160 | let key = httpTransaction.id;
161 | let name = httpTransaction.name;
162 | let httpTransactionCounter = httpTransaction.counter;
163 | return '' + name + ' (' + httpTransactionCounter + ' ) ';
164 | }
165 |
166 | function transactionHeader(numOfTransactions) {
167 | $('.transaction-title label').html(numOfTransactions + ' 测试用例 / 标签');
168 | }
169 |
170 | function addNewTransaction(transactionName) {
171 | if (!stringIsEmpty(transactionName)) {
172 | // 通过消息传递添加transaction
173 | chrome.runtime.sendMessage({
174 | action: 'add_transaction',
175 | name: transactionName
176 | }, (response) => {
177 | if (response && response.transaction) {
178 | // 本地也添加到transactions数组中
179 | transactions.httpTransactions.push(response.transaction);
180 |
181 | let httpLength = transactions.httpTransactions.length;
182 | transactionHeader(httpLength);
183 |
184 | listUl.append(liType(response.transaction));
185 |
186 | addBtn.addClass('disabled').attr('disabled', 'disabled');
187 | nameInput.attr('disabled', 'disabled');
188 | chrome.runtime.sendMessage({ action: 'update_transactions' });
189 | }
190 | });
191 | }
192 | }
193 |
194 | function toggleAddTransaction() {
195 | console.log('toggleAddTransaction called');
196 | // 首先检查录制状态
197 | chrome.runtime.sendMessage({ action: 'check_status' }, (response) => {
198 | console.log('Recording status response:', response);
199 | if (response && response.status) {
200 | let isRecording = response.status === 'recording';
201 | console.log('Is recording:', isRecording);
202 |
203 | if (!isRecording) {
204 | // 如果没有在录制,禁用输入并提示用户
205 | console.log('Not recording, disabling input');
206 | nameInput.val('').attr('disabled', true);
207 | nameInput.attr('placeholder', '请先开始录制...');
208 | addBtn.addClass('disabled').attr('disabled', true);
209 | return;
210 | }
211 |
212 | // 如果在录制状态,检查是否有transactions以及最后一个transaction是否有计数
213 | console.log('Transactions count:', transactions.httpTransactions.length);
214 | if (transactions.httpTransactions.length === 0) {
215 | // 没有transactions,可以添加第一个
216 | console.log('No transactions, enabling input for first transaction');
217 | nameInput.removeAttr('disabled');
218 | nameInput.attr('placeholder', '1 测试用例 / 标签');
219 | if (!stringIsEmpty(nameInput.val())) {
220 | addBtn.removeClass('disabled').removeAttr('disabled');
221 | }
222 | } else {
223 | // 有transactions,检查最后一个的计数
224 | let lastTransaction = transactions.httpTransactions[transactions.httpTransactions.length - 1];
225 | let disable = lastTransaction.counter === 0;
226 | console.log('Last transaction counter:', lastTransaction.counter, 'Disable:', disable);
227 |
228 | if (disable) {
229 | nameInput.val('').attr('disabled', true);
230 | nameInput.attr('placeholder', '请先完成当前测试用例的操作...');
231 | addBtn.addClass('disabled').attr('disabled', true);
232 | } else {
233 | nameInput.removeAttr('disabled');
234 | let nextNumber = transactions.httpTransactions.length + 1;
235 | nameInput.attr('placeholder', `${nextNumber} 测试用例 / 标签`);
236 | if (!stringIsEmpty(nameInput.val())) {
237 | addBtn.removeClass('disabled').removeAttr('disabled');
238 | }
239 | }
240 | }
241 | } else {
242 | // 如果无法获取状态,默认禁用
243 | console.log('Cannot get recording status, disabling input');
244 | nameInput.val('').attr('disabled', true);
245 | nameInput.attr('placeholder', '无法连接到后台服务...');
246 | addBtn.addClass('disabled').attr('disabled', true);
247 | }
248 | });
249 | }
250 |
251 | function stringIsEmpty(string) {
252 | return !/\S/.test(string);
253 | }
254 |
255 | function renderTransactions() {
256 | // 从后台获取最新的transactions数据
257 | chrome.runtime.sendMessage({ action: 'get_transactions' }, (response) => {
258 | if (response && response.transactions) {
259 | transactions = response.transactions;
260 |
261 | listUl.html('');
262 | let localHttpTransactions = transactions.httpTransactions;
263 | localHttpTransactions.forEach(function (transaction) {
264 | listUl.append(liType(transaction));
265 | });
266 |
267 | if (localHttpTransactions.length > 0) {
268 | transactionHeader(localHttpTransactions.length);
269 | $('#transaction-list').scrollTop(listUl[0].scrollHeight);
270 | let lastTransaction = localHttpTransactions[localHttpTransactions.length - 1];
271 | $('#transactions li:last-child .http-transaction-counter').html(lastTransaction.counter);
272 | }
273 | toggleAddTransaction();
274 | }
275 | });
276 | }
277 |
278 | chrome.runtime.onMessage.addListener(function (request) {
279 | switch (request.action) {
280 | case "update_transactions":
281 | renderTransactions();
282 | break;
283 | case "recording_status_changed":
284 | // 录制状态改变时,更新UI
285 | toggleAddTransaction();
286 | break;
287 | }
288 | });
289 |
290 | addMacClass();
291 |
292 | renderTransactions();
293 | });
294 |
295 |
--------------------------------------------------------------------------------