├── jsoneditor └── jsoneditor.js ├── .gitignore ├── img ├── record.png ├── save.png ├── stop.png ├── download.png └── wechat-group.png ├── icon ├── favicon.png ├── favicon-16.png ├── favicon-48.png ├── favicon-w.png ├── favicon-128.png └── metersphere.png ├── panel ├── images │ ├── sidebar.png │ ├── header_bg.png │ ├── icn_audio.png │ ├── icn_edit.png │ ├── icn_photo.png │ ├── icn_tags.png │ ├── icn_trash.png │ ├── icn_user.png │ ├── icn_video.png │ ├── btn_submit.png │ ├── btn_submit_2.png │ ├── icn_add_user.png │ ├── icn_folder.png │ ├── icn_logout.png │ ├── icn_profile.png │ ├── icn_search.png │ ├── icn_security.png │ ├── icn_settings.png │ ├── post_message.png │ ├── btn_view_site.png │ ├── header_shadow.png │ ├── icn_alert_info.png │ ├── icn_categories.png │ ├── icn_jump_back.png │ ├── icn_view_users.png │ ├── secondary_bar.png │ ├── sidebar_shadow.png │ ├── icn_alert_error.png │ ├── icn_alert_success.png │ ├── icn_alert_warning.png │ ├── icn_edit_article.png │ ├── icn_new_article.png │ ├── module_footer_bg.png │ ├── sidebar_divider.png │ ├── breadcrumb_divider.png │ ├── table_sorter_header.png │ └── secondary_bar_shadow.png ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── js │ ├── lib │ │ ├── jquery.tabletab.js │ │ ├── jquery.equalHeight.js │ │ ├── utils.js │ │ └── colResizable-1.5.min.js │ ├── UI │ │ ├── options.js │ │ ├── log.js │ │ ├── sortable-ui.js │ │ ├── hideshow.js │ │ ├── command_grid_toolbar.js │ │ └── panelSetting.js │ ├── background │ │ ├── initial.js │ │ ├── formatCommand.js │ │ ├── editor.js │ │ ├── doc.js │ │ └── window-controller.js │ └── IO │ │ ├── load_file.js │ │ ├── inputFileTransformer.js │ │ └── save_file.js └── css │ ├── ie.css │ ├── options.css │ └── jquery-ui-slider-pips.css ├── bootstrap └── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── content ├── runScript-injecter.js ├── targetSelecter.js ├── prompt-injecter.js ├── recorder.js └── command-receiver.js ├── page ├── runScript.js └── prompt.js ├── html ├── transaction-controls.html └── transaction-ui.html ├── manifest.json ├── README.md ├── js ├── transaction-ui-controls.js ├── editor.js ├── main.js ├── content-script.js └── transaction-ui.js ├── editor.html ├── background └── background.js ├── main.html └── common └── escape.js /jsoneditor/jsoneditor.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | .idea/ 3 | .DS_Store -------------------------------------------------------------------------------- /img/record.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/img/record.png -------------------------------------------------------------------------------- /img/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/img/save.png -------------------------------------------------------------------------------- /img/stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/img/stop.png -------------------------------------------------------------------------------- /icon/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/icon/favicon.png -------------------------------------------------------------------------------- /img/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/img/download.png -------------------------------------------------------------------------------- /icon/favicon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/icon/favicon-16.png -------------------------------------------------------------------------------- /icon/favicon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/icon/favicon-48.png -------------------------------------------------------------------------------- /icon/favicon-w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/icon/favicon-w.png -------------------------------------------------------------------------------- /icon/favicon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/icon/favicon-128.png -------------------------------------------------------------------------------- /icon/metersphere.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/icon/metersphere.png -------------------------------------------------------------------------------- /img/wechat-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/img/wechat-group.png -------------------------------------------------------------------------------- /panel/images/sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/sidebar.png -------------------------------------------------------------------------------- /panel/images/header_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/header_bg.png -------------------------------------------------------------------------------- /panel/images/icn_audio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_audio.png -------------------------------------------------------------------------------- /panel/images/icn_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_edit.png -------------------------------------------------------------------------------- /panel/images/icn_photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_photo.png -------------------------------------------------------------------------------- /panel/images/icn_tags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_tags.png -------------------------------------------------------------------------------- /panel/images/icn_trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_trash.png -------------------------------------------------------------------------------- /panel/images/icn_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_user.png -------------------------------------------------------------------------------- /panel/images/icn_video.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_video.png -------------------------------------------------------------------------------- /panel/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /panel/images/btn_submit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/btn_submit.png -------------------------------------------------------------------------------- /panel/images/btn_submit_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/btn_submit_2.png -------------------------------------------------------------------------------- /panel/images/icn_add_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_add_user.png -------------------------------------------------------------------------------- /panel/images/icn_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_folder.png -------------------------------------------------------------------------------- /panel/images/icn_logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_logout.png -------------------------------------------------------------------------------- /panel/images/icn_profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_profile.png -------------------------------------------------------------------------------- /panel/images/icn_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_search.png -------------------------------------------------------------------------------- /panel/images/icn_security.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_security.png -------------------------------------------------------------------------------- /panel/images/icn_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_settings.png -------------------------------------------------------------------------------- /panel/images/post_message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/post_message.png -------------------------------------------------------------------------------- /panel/images/btn_view_site.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/btn_view_site.png -------------------------------------------------------------------------------- /panel/images/header_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/header_shadow.png -------------------------------------------------------------------------------- /panel/images/icn_alert_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_alert_info.png -------------------------------------------------------------------------------- /panel/images/icn_categories.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_categories.png -------------------------------------------------------------------------------- /panel/images/icn_jump_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_jump_back.png -------------------------------------------------------------------------------- /panel/images/icn_view_users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_view_users.png -------------------------------------------------------------------------------- /panel/images/secondary_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/secondary_bar.png -------------------------------------------------------------------------------- /panel/images/sidebar_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/sidebar_shadow.png -------------------------------------------------------------------------------- /panel/images/icn_alert_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_alert_error.png -------------------------------------------------------------------------------- /panel/images/icn_alert_success.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_alert_success.png -------------------------------------------------------------------------------- /panel/images/icn_alert_warning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_alert_warning.png -------------------------------------------------------------------------------- /panel/images/icn_edit_article.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_edit_article.png -------------------------------------------------------------------------------- /panel/images/icn_new_article.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/icn_new_article.png -------------------------------------------------------------------------------- /panel/images/module_footer_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/module_footer_bg.png -------------------------------------------------------------------------------- /panel/images/sidebar_divider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/sidebar_divider.png -------------------------------------------------------------------------------- /panel/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /panel/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /panel/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /panel/images/breadcrumb_divider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/breadcrumb_divider.png -------------------------------------------------------------------------------- /panel/images/table_sorter_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/table_sorter_header.png -------------------------------------------------------------------------------- /panel/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /panel/images/secondary_bar_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/panel/images/secondary_bar_shadow.png -------------------------------------------------------------------------------- /bootstrap/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/bootstrap/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /bootstrap/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/bootstrap/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /bootstrap/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/bootstrap/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /bootstrap/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/metersphere/chrome-extensions/HEAD/bootstrap/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /panel/js/lib/jquery.tabletab.js: -------------------------------------------------------------------------------- 1 | // make sure the $ is pointing to JQuery and not some other library 2 | $(document).ready(function() { 3 | $(".tablesorter").tablesorter(); 4 | }); 5 | -------------------------------------------------------------------------------- /panel/js/lib/jquery.equalHeight.js: -------------------------------------------------------------------------------- 1 | // make sure the $ is pointing to JQuery and not some other library 2 | (function($) { 3 | // add a new method to JQuery 4 | 5 | $.fn.equalHeight = function() { 6 | // find the tallest height in the collection 7 | // that was passed in (.column) 8 | tallest = 0; 9 | this.each(function() { 10 | thisHeight = $(this).height(); 11 | if (thisHeight > tallest) 12 | tallest = thisHeight; 13 | }); 14 | 15 | // set each items height to use the tallest value found 16 | this.each(function() { 17 | $(this).height(tallest); 18 | }); 19 | } 20 | })(jQuery); 21 | -------------------------------------------------------------------------------- /panel/css/ie.css: -------------------------------------------------------------------------------- 1 | .quick_search { 2 | text-align: center; 3 | padding: 14px 0 0px 0; 4 | } 5 | 6 | .quick_search input[type=text] { 7 | text-align: left; 8 | height: 22px; 9 | width: 88%; 10 | color: #ccc; 11 | padding-left: 2%; 12 | padding-top: 5px; 13 | background: #fff url(../images/icn_search.png) no-repeat; 14 | background-position: 10px 6px; 15 | } 16 | 17 | .toggleLink { 18 | display: inline; 19 | float: none; 20 | margin-left: 2% 21 | } 22 | 23 | html ul.tabs li.active, 24 | html ul.tabs li.active a:hover { 25 | background: #ccc; 26 | } 27 | 28 | input[type=submit].btn_post_message { 29 | background: url(../images/post_message.png) no-repeat; 30 | } 31 | 32 | fieldset input[type=text] { 33 | margin-left: -10px; 34 | } 35 | 36 | fieldset select { 37 | margin-left: -10px 38 | } 39 | 40 | fieldset textarea { 41 | margin-left: -10px; 42 | } 43 | -------------------------------------------------------------------------------- /panel/js/UI/options.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 | browser.storage.sync.get("tac") 21 | .then((res) => { 22 | $("#tac").prop("checked", res.tac); 23 | }); 24 | 25 | $("#tac").click(function() { 26 | browser.storage.sync.set({ 27 | tac: $("#tac").prop("checked") 28 | }); 29 | }); 30 | 31 | }); -------------------------------------------------------------------------------- /content/runScript-injecter.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 elementForInjectingScript = document.createElement("script"); 19 | elementForInjectingScript.src = browser.runtime.getURL("page/runScript.js"); 20 | (document.head || document.documentElement).appendChild(elementForInjectingScript); 21 | 22 | window.addEventListener("message", function(event) { 23 | if (event.source.top == window && event.data && event.data.direction == "from-page-runscript") { 24 | selenium.browserbot.runScriptResponse = true; 25 | selenium.browserbot.runScriptMessage = event.data.result; 26 | } 27 | }); -------------------------------------------------------------------------------- /page/runScript.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 | var isWanted = false 18 | window.onerror = function(msg){ 19 | if(isWanted){ 20 | window.postMessage({ 21 | direction: "from-page-runscript", 22 | result: msg 23 | }, "*"); 24 | isWanted = false; 25 | } 26 | }; 27 | window.addEventListener("message", function(event) { 28 | if (event.source == window && event.data && event.data.direction == "from-content-runscript") { 29 | isWanted = true; 30 | var doc = window.document; 31 | var scriptTag = doc.createElement("script"); 32 | scriptTag.type = "text/javascript" 33 | scriptTag.text = event.data.script; 34 | doc.body.appendChild(scriptTag); 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /panel/js/background/initial.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 sideex_wait = { 19 | next_command_wait: false, 20 | done: true 21 | }; 22 | 23 | var sideex_testCase = { 24 | count: 0 25 | }; 26 | 27 | var sideex_testSuite = { 28 | count: 0 29 | }; 30 | 31 | function clean_panel() { 32 | emptyNode(document.getElementById("records-grid")); 33 | emptyNode(document.getElementById("command-target-list")); 34 | emptyNode(document.getElementById("target-dropdown")); 35 | document.getElementById("command-command").value = ""; 36 | document.getElementById("command-target").value = ""; 37 | document.getElementById("command-value").value = ""; 38 | } 39 | -------------------------------------------------------------------------------- /panel/css/options.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2005 Shinya Kasatani 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 | body { 18 | overflow: hidden; 19 | } 20 | 21 | div { 22 | width: 500px; 23 | } 24 | 25 | .selection { 26 | padding: 10px; 27 | } 28 | 29 | input[type="checkbox"] { 30 | display: none; 31 | } 32 | 33 | label div{ 34 | width: 20px; 35 | height: 20px; 36 | display: inline-block; 37 | border: 2px solid #ededed; 38 | text-align: center; 39 | line-height: 20px; 40 | margin-right: 10px; 41 | border-radius: 5px; 42 | cursor: pointer; 43 | } 44 | 45 | label i{ 46 | font-size: 16px; 47 | opacity: 0; 48 | } 49 | 50 | label:hover div{ 51 | background: #dbdbdb; 52 | } 53 | 54 | input:checked + label i{ 55 | opacity: 1; 56 | } 57 | input:checked + label div{ 58 | background: #dbdbdb; 59 | } -------------------------------------------------------------------------------- /panel/js/UI/log.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 | class Log { 18 | 19 | constructor(container) { 20 | this.container = container; 21 | } 22 | 23 | log(str) { 24 | this._write(str, "log-info"); 25 | } 26 | 27 | info(str) { 28 | this._write("[info] " + str, "log-info"); 29 | } 30 | 31 | error(str) { 32 | this._write("[error] " + str, "log-error"); 33 | }; 34 | 35 | _write(str, className) { 36 | let textElement = document.createElement('h4'); 37 | textElement.setAttribute("class", className); 38 | textElement.textContent = str; 39 | this.container.appendChild(textElement); 40 | this.container.scrollIntoView(false); 41 | } 42 | } 43 | 44 | // TODO: new by another object(s) 45 | var sideex_log = new Log(document.getElementById("logcontainer")); 46 | var help_log = new Log(document.getElementById("refercontainer")); 47 | 48 | document.getElementById("clear-log").addEventListener("click", function() { 49 | emptyNode(document.getElementById("logcontainer")); 50 | }, false); 51 | -------------------------------------------------------------------------------- /html/transaction-controls.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Recorder Controller 6 | 7 | 44 | 45 | 46 |
47 | 50 | 51 | 54 | 55 | 58 |
59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "MeterSphere", 4 | "description": "MeterSphere Recorder", 5 | "version": "2.0.0", 6 | "homepage_url": "https://www.metersphere.io", 7 | "action": { 8 | "default_icon": "icon/favicon.png", 9 | "default_popup": "main.html", 10 | "default_title": "MeterSphere" 11 | }, 12 | "icons": { 13 | "16": "icon/favicon-16.png", 14 | "48": "icon/favicon.png", 15 | "128": "icon/favicon-128.png" 16 | }, 17 | "background": { 18 | "service_worker": "js/background.js" 19 | }, 20 | "content_scripts": [ 21 | { 22 | "all_frames": false, 23 | "js": [ 24 | "common/browser-polyfill.js", 25 | "jquery/jquery-3.4.1.min.js", 26 | "jquery/jquery-ui.min.js", 27 | "js/content-script.js" 28 | ], 29 | "matches": [ 30 | "http://*/*", 31 | "https://*/*" 32 | ], 33 | "run_at": "document_start" 34 | } 35 | ], 36 | "permissions": [ 37 | "tabs", 38 | "activeTab", 39 | "webRequest", 40 | "declarativeNetRequest", 41 | "contextMenus", 42 | "downloads", 43 | "webNavigation", 44 | "notifications", 45 | "storage", 46 | "unlimitedStorage", 47 | "browsingData" 48 | ], 49 | "host_permissions": [ 50 | "http://*/*", 51 | "https://*/*" 52 | ], 53 | "web_accessible_resources": [ 54 | { 55 | "resources": [ 56 | "html/transaction-ui.html", 57 | "html/transaction-controls.html" 58 | ], 59 | "matches": [ 60 | "http://*/*", 61 | "https://*/*" 62 | ] 63 | } 64 | ] 65 | } -------------------------------------------------------------------------------- /panel/js/background/formatCommand.js: -------------------------------------------------------------------------------- 1 | // Modified in remoteControl.js from selenium-IDE 2 | 3 | var declaredVars = {}; 4 | 5 | function xlateArgument(value) { 6 | value = value.replace(/^\s+/, ''); 7 | value = value.replace(/\s+$/, ''); 8 | var r; 9 | var r2; 10 | var parts = []; 11 | if ((r = /\$\{/.exec(value))) { 12 | var regexp = /\$\{(.*?)\}/g; 13 | var lastIndex = 0; 14 | while (r2 = regexp.exec(value)) { 15 | if (declaredVars[r2[1]]) { 16 | if (r2.index - lastIndex > 0) { 17 | parts.push(string(value.substring(lastIndex, r2.index))); 18 | } 19 | parts.push(declaredVars[r2[1]]); 20 | lastIndex = regexp.lastIndex; 21 | } else if (r2[1] == "nbsp") { 22 | if (r2.index - lastIndex > 0) { 23 | parts.push(declaredVars[string(value.substring(lastIndex, r2.index))]); 24 | } 25 | parts.push(nonBreakingSpace()); 26 | lastIndex = regexp.lastIndex; 27 | } 28 | } 29 | if (lastIndex < value.length) { 30 | parts.push(string(value.substring(lastIndex, value.length))); 31 | } 32 | return parts.join(""); 33 | } else { 34 | return string(value); 35 | } 36 | } 37 | 38 | function string(value) { 39 | if (value != null) { 40 | value = value.replace(/\\/g, '\\\\'); 41 | value = value.replace(/\"/g, '\\"'); 42 | value = value.replace(/\r/g, '\\r'); 43 | value = value.replace(/\n/g, '\\n'); 44 | return value; 45 | } else { 46 | return ''; 47 | } 48 | } 49 | 50 | function handleFormatCommand(message, sender, response) { 51 | if (message.storeStr) { 52 | declaredVars[message.storeVar] = message.storeStr; 53 | } else if (message.echoStr) 54 | sideex_log.info("echo: " + message.echoStr); 55 | } 56 | 57 | browser.runtime.onMessage.addListener(handleFormatCommand); 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MeterSphere 浏览器插件 2 | 3 | [![TesterHome](https://img.shields.io/badge/TTF-TesterHome-2955C5.svg)](https://testerhome.com/github_statistics) 4 | 5 | [MeterSphere](https://github.com/metersphere/metersphere) 是一站式开源持续测试平台,涵盖测试跟踪、接口测试、性能测试、团队协作等功能,兼容JMeter 等开源标准,有效助力开发和测试团队充分利用云弹性进行高度可扩展的自动化测试,加速高质量软件的交付。 6 | 该项目为 MeterSphere 配套的浏览器插件,该插件可将用户在浏览器操作时的 HTTP 请求记录下来并生成 JMX 文件(JMeter 脚本文件),用于在 MeterSphere 中进行接口测试或性能测试。 7 | 8 | ## 安装使用 9 | 10 | ### 开发者模式安装 11 | 1. 谷歌浏览器输入chrome://extensions/ 进入扩展程序安装界面,打开开发者模式 12 | 2. 导入扩展程序以下两种方式 13 | - git clone 该项目到本地,选择「加载已解压的扩展程序」选择该项目目录进行安装 14 | - 在该项目的 [release](https://github.com/metersphere/chrome-extensions/releases) 页面下载最新版本的 zip 包,解压 zip 后,选择「加载已解压的扩展程序」选择解压后的目录进行安装 15 | 16 | ### 使用指导 17 | 18 | 插件安装后,点击浏览器插件列表中该插件图标,在弹出页面中可以修改录制脚本的名称,点击开始录制按钮 19 | 20 | ![record](./img/record.png) 21 | 22 | 访问需要进行录制的站点,进行正常使用操作,浏览器中的所有网络请求均会被记录下来 23 | 24 | 当操作完成后,点击插件界面的停止按钮停止录制 25 | 26 | ![stop](./img/stop.png) 27 | 28 | 录制停止后,点击插件界面的保存按钮进行保存 29 | 30 | ![save](./img/save.png) 31 | 32 | 插件弹出所有记录到请求的站点列表,勾选需要保留的站点请求点击下载按钮,下载 JMX 脚本至本地 33 | 34 | ![download](./img/download.png) 35 | 36 | ## 了解更多 37 | 38 | - [MeterSphere 官网](https://metersphere.io) 39 | - [MeterSphere 文档](https://metersphere.io/docs) 40 | - [演示视频](https://metersphere.oss-cn-hangzhou.aliyuncs.com/metersphere_demo.mp4) 41 | 42 | ## 微信群 43 | 44 | ![wechat-group](https://metersphere.oss-cn-hangzhou.aliyuncs.com/img/wechat-group.png) 45 | 46 | ## License & Copyright 47 | 48 | Copyright (c) 2014-2025 飞致云 FIT2CLOUD, All rights reserved. 49 | 50 | Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 51 | 52 | https://www.gnu.org/licenses/gpl-3.0.html 53 | 54 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 55 | -------------------------------------------------------------------------------- /js/transaction-ui-controls.js: -------------------------------------------------------------------------------- 1 | function showButtons() { 2 | for (let i = 0; i < arguments.length; i++) { 3 | $("#" + arguments[i]).show(); 4 | } 5 | } 6 | 7 | function hideButtons() { 8 | for (let i = 0; i < arguments.length; i++) { 9 | $("#" + arguments[i]).hide(); 10 | } 11 | } 12 | 13 | function switchButtons(status) { 14 | switch (status) { 15 | case "recording": 16 | hideButtons('resume'); 17 | showButtons('pause', 'stop'); 18 | break; 19 | case "pause": 20 | hideButtons('pause'); 21 | showButtons('stop', 'resume'); 22 | break; 23 | case "stopped": 24 | hideButtons('pause', 'stop'); 25 | showButtons('resume'); 26 | break; 27 | } 28 | } 29 | 30 | function updateButtons() { 31 | chrome.runtime.sendMessage({action: "check_status"}, function (response) { 32 | let status = response.status; 33 | switchButtons(status); 34 | }); 35 | } 36 | 37 | $(document).ready(function () { 38 | 39 | updateButtons(); 40 | 41 | $('#resume').click(function () { 42 | switchButtons("recording"); 43 | chrome.runtime.sendMessage({action: "resume_recording"}); 44 | chrome.runtime.sendMessage({action: "update_buttons"}); 45 | }); 46 | $('#pause').click(function () { 47 | switchButtons("pause"); 48 | chrome.runtime.sendMessage({action: "pause_recording"}); 49 | chrome.runtime.sendMessage({action: "update_buttons"}); 50 | }); 51 | $('#stop').click(function () { 52 | switchButtons("stopped"); 53 | chrome.runtime.sendMessage({action: "stop_recording"}); 54 | chrome.runtime.sendMessage({action: "update_buttons"}); 55 | }); 56 | 57 | // 同步所有Tab 58 | chrome.runtime.onMessage.addListener(function (request) { 59 | switch (request.action) { 60 | case "update_buttons": 61 | updateButtons(); 62 | break; 63 | } 64 | }); 65 | }); 66 | 67 | function isMacintosh() { 68 | return navigator.platform.indexOf('Mac') > -1 69 | } 70 | 71 | function isWindows() { 72 | return navigator.platform.indexOf('Win') > -1 73 | } 74 | -------------------------------------------------------------------------------- /panel/js/lib/utils.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 | /** 19 | * Remove all child nodes of a node. 20 | * @argument {Node} node - A node to remove all child nodes. 21 | */ 22 | function emptyNode(node) { 23 | while (node.firstChild) { 24 | node.removeChild(node.firstChild); 25 | } 26 | } 27 | 28 | /** 29 | * Clone or cut child nodes of a node, and then append to another node. 30 | * @param {Node} dest - A node which child nodes should be appended to. 31 | * @param {Node} src - A node which is the source of child nodes. 32 | * @param {boolean} clone - Determine if child nodes should be cloned instead of moved. 33 | * @param {boolean} deep - If cloned, determine if descendants of child nodes should also be cloned. 34 | */ 35 | function appendChildNodes(dest, src, clone, deep) { 36 | if (clone) { 37 | let children = src.childNodes; 38 | for (let i = 0; i < children.length; i++) { 39 | dest.appendChild(children[i].cloneNode(deep)); 40 | } 41 | } else { 42 | while(src.firstChild) { 43 | dest.appendChild(src.firstChild); 44 | } 45 | } 46 | } 47 | 48 | /** 49 | * Clear all child nodes of a node and clone or move child nodes from another node. 50 | * @param {Node} dest - A node which child nodes should be copied to. 51 | * @param {Node} src - A node which is the source of child nodes. 52 | * @param {boolean} clone - Determine if child nodes should be cloned instead of moved. 53 | * @param {boolean} deep - If cloned, determine if descendants of child nodes should also be cloned. 54 | */ 55 | function assignChildNodes(dest, src, clone, deep) { 56 | emptyNode(dest); 57 | appendChildNodes(dest, src, clone, deep); 58 | } 59 | -------------------------------------------------------------------------------- /content/targetSelecter.js: -------------------------------------------------------------------------------- 1 | // Modified in tools.js from selenium-IDE 2 | 3 | function TargetSelecter(callback, cleanupCallback) { 4 | this.callback = callback; 5 | this.cleanupCallback = cleanupCallback; 6 | 7 | // This is for XPCOM/XUL addon and can't be used 8 | //var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator); 9 | //this.win = wm.getMostRecentWindow('navigator:browser').getBrowser().contentWindow; 10 | 11 | // Instead, we simply assign global content window to this.win 12 | this.win = window; 13 | var doc = this.win.document; 14 | var div = doc.createElement("div"); 15 | div.setAttribute("style", "display: none;"); 16 | doc.body.insertBefore(div, doc.body.firstChild); 17 | this.div = div; 18 | this.e = null; 19 | this.r = null; 20 | doc.addEventListener("mousemove", this, true); 21 | doc.addEventListener("click", this, true); 22 | } 23 | 24 | TargetSelecter.prototype.cleanup = function () { 25 | try { 26 | if (this.div) { 27 | if (this.div.parentNode) { 28 | this.div.parentNode.removeChild(this.div); 29 | } 30 | this.div = null; 31 | } 32 | if (this.win) { 33 | var doc = this.win.document; 34 | doc.removeEventListener("mousemove", this, true); 35 | doc.removeEventListener("click", this, true); 36 | } 37 | } catch (e) { 38 | if (e != "TypeError: can't access dead object") { 39 | throw e; 40 | } 41 | } 42 | this.win = null; 43 | if (this.cleanupCallback) { 44 | this.cleanupCallback(); 45 | } 46 | }; 47 | 48 | TargetSelecter.prototype.handleEvent = function (evt) { 49 | switch (evt.type) { 50 | case "mousemove": 51 | this.highlight(evt.target.ownerDocument, evt.clientX, evt.clientY); 52 | break; 53 | case "click": 54 | if (evt.button == 0 && this.e && this.callback) { 55 | this.callback(this.e, this.win); 56 | } //Right click would cancel the select 57 | evt.preventDefault(); 58 | evt.stopPropagation(); 59 | this.cleanup(); 60 | break; 61 | } 62 | }; 63 | 64 | TargetSelecter.prototype.highlight = function (doc, x, y) { 65 | if (doc) { 66 | var e = doc.elementFromPoint(x, y); 67 | if (e && e != this.e) { 68 | this.highlightElement(e); 69 | } 70 | } 71 | } 72 | 73 | TargetSelecter.prototype.highlightElement = function (element) { 74 | if (element && element != this.e) { 75 | this.e = element; 76 | } else { 77 | return; 78 | } 79 | var r = element.getBoundingClientRect(); 80 | var or = this.r; 81 | if (r.left >= 0 && r.top >= 0 && r.width > 0 && r.height > 0) { 82 | if (or && r.top == or.top && r.left == or.left && r.width == or.width && r.height == or.height) { 83 | return; 84 | } 85 | this.r = r; 86 | var style = "pointer-events: none; position: absolute; box-shadow: 0 0 0 1px black; outline: 1px dashed white; outline-offset: -1px; background-color: rgba(250,250,128,0.4); z-index: 100;"; 87 | var pos = "top:" + (r.top + this.win.scrollY) + "px; left:" + (r.left + this.win.scrollX) + "px; width:" + r.width + "px; height:" + r.height + "px;"; 88 | this.div.setAttribute("style", style + pos); 89 | } else if (or) { 90 | this.div.setAttribute("style", "display: none;"); 91 | } 92 | }; -------------------------------------------------------------------------------- /content/prompt-injecter.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 elementForInjectingScript = document.createElement("script"); 19 | elementForInjectingScript.src = browser.runtime.getURL("page/prompt.js"); 20 | (document.head || document.documentElement).appendChild(elementForInjectingScript); 21 | 22 | 23 | if (window === window.top) { 24 | window.addEventListener("message", function(event) { 25 | if (event.source.top == window && event.data && 26 | event.data.direction == "from-page-script") { 27 | if (event.data.recordedType) { 28 | switch (event.data.recordedType) { 29 | case "prompt": 30 | if (event.data.recordedResult != null) { 31 | recorder.record("answerOnNextPrompt", [[event.data.recordedResult]], "", true, event.data.frameLocation); 32 | } else { 33 | recorder.record("chooseCancelOnNextPrompt", [[""]], "", true, event.data.frameLocation); 34 | } 35 | recorder.record("assertPrompt", [[event.data.recordedMessage]], "", false, event.data.frameLocation); 36 | break; 37 | case "confirm": 38 | if (event.data.recordedResult == true) { 39 | recorder.record("chooseOkOnNextConfirmation", [[""]], "", true, event.data.frameLocation); 40 | } else { 41 | recorder.record("chooseCancelOnNextConfirmation", [[""]], "", true, event.data.frameLocation); 42 | } 43 | recorder.record("assertConfirmation", [[event.data.recordedMessage]], "", false, event.data.frameLocation); 44 | break; 45 | case "alert": 46 | //record("answerOnNextAlert",[[event.data.recordedResult]],"",true); 47 | recorder.record("assertAlert", [[event.data.recordedMessage]], "", false, event.data.frameLocation); 48 | break; 49 | } 50 | } 51 | if (event.data.response) { 52 | switch (event.data.response) { 53 | case "prompt": 54 | selenium.browserbot.promptResponse = true; 55 | if (event.data.value) 56 | selenium.browserbot.promptMessage = event.data.value; 57 | break; 58 | case "confirm": 59 | selenium.browserbot.confirmationResponse = true; 60 | if (event.data.value) 61 | selenium.browserbot.confirmationMessage = event.data.value; 62 | break; 63 | case "alert": 64 | selenium.browserbot.alertResponse = true; 65 | if(event.data.value) 66 | selenium.browserbot.alertMessage = event.data.value; 67 | break; 68 | } 69 | } 70 | } 71 | }) 72 | } -------------------------------------------------------------------------------- /panel/js/UI/sortable-ui.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 | // set testsuite and record-grid sortable 19 | $(document).ready(function() { 20 | $("#records-grid").sortable({ 21 | axis: "y", 22 | items: "tr", 23 | scroll: true, 24 | revert: 200, 25 | scrollSensitivity: 20, 26 | connectWith: "#records-grid", 27 | helper: function(e, tr) { 28 | var $originals = tr.children(); 29 | var $helper = tr.clone(); 30 | $helper.children().each(function(index) { 31 | $(this).width($originals.eq(index).width()); 32 | }); 33 | return $helper; 34 | }, 35 | update: function(event, ui) { 36 | getSelectedCase().classList.add("modified"); 37 | getSelectedSuite().getElementsByTagName("strong")[0].classList.add("modified"); 38 | closeConfirm(true); 39 | 40 | // re-assign id 41 | var start_ID = ui.item.attr("id"), 42 | end_ID = ui.item.prev().attr("id"); 43 | reAssignId(start_ID, (end_ID.includes("count")?"records-0":(end_ID))); 44 | 45 | // show in command-toolbar 46 | $('#records-grid .selectedRecord').removeClass('selectedRecord'); 47 | $(".record-bottom").removeClass("active"); 48 | ui.item.addClass('selectedRecord'); 49 | // do not forget that textNode is also a node 50 | document.getElementById("command-command").value = getCommandName(ui.item[0]); 51 | document.getElementById("command-target").value = getCommandTarget(ui.item[0], true); 52 | document.getElementById("command-value").value = getCommandValue(ui.item[0]); 53 | 54 | // store command grid to testCase 55 | var s_case = getSelectedCase(); 56 | if (s_case) { 57 | sideex_testCase[s_case.id].records = document.getElementById("records-grid").innerHTML; 58 | } 59 | } 60 | }); 61 | 62 | $("#testCase-container").sortable({ 63 | axis: "y", 64 | handle: "strong", 65 | items: ".message", 66 | scroll: true, 67 | revert: 300, 68 | scrollSensitivity: 20, 69 | start: function(event, ui) { 70 | ui.placeholder.height(ui.item.height()); 71 | } 72 | }); 73 | }); 74 | 75 | // make case sortable when addTestSuite 76 | function makeCaseSortable(suite) { 77 | var prevSuite = null; 78 | $(suite).sortable({ 79 | axis: "y", 80 | items: "p", 81 | scroll: true, 82 | revert: 300, 83 | scrollSensitivity: 20, 84 | connectWith: ".message", 85 | start: function(event, ui) { 86 | ui.placeholder.html(ui.item.html()).css({ "visibility": "visible", "opacity": 0.3 }); 87 | prevSuite = event.target; 88 | }, 89 | update: function(event, ui) { 90 | if (prevSuite !== event.target) 91 | $(prevSuite).find("strong").addClass("modified"); 92 | $(event.target).find("strong").addClass("modified"); 93 | closeConfirm(true); 94 | } 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /js/editor.js: -------------------------------------------------------------------------------- 1 | let downloadRecording = new DownloadRecording(); 2 | 3 | $(document).ready(function () { 4 | let name = $('#name'); 5 | let transactions; 6 | chrome.storage.local.get('jmxName', item => { 7 | name.val(item.jmxName); 8 | }) 9 | 10 | let container = $('#json-editor')[0]; 11 | let options = {} 12 | let editor = new JSONEditor(container, options); 13 | chrome.storage.local.get('traffic', item => { 14 | editor.set(JSON.parse(item.traffic)); 15 | }) 16 | 17 | $('#export-json').click(() => { 18 | transactions = checkTransactions(editor.get()); 19 | downloadRecording.downloadJSON(name.val(), transactions); 20 | }); 21 | 22 | $('#export-jmx').click(() => { 23 | transactions = checkTransactions(editor.get()); 24 | let domains = downloadRecording.getDomains(transactions); 25 | if (domains.length > 1) { 26 | let domainsDiv = $('#domains'); 27 | domainsDiv.empty(); 28 | domains.forEach(domain => { 29 | domainsDiv.prepend( 30 | '
' + 31 | ' ' + 32 | ' ' + 33 | '
' 34 | ) 35 | }) 36 | $("#json-editor").hide(); 37 | $("#domains-select").show(); 38 | } else { 39 | downloadRecording.downloadJMX(name.val(), domains, transactions); 40 | } 41 | }); 42 | 43 | $('#submit').click(() => { 44 | let checked = []; 45 | $("input[name='domains']:checked").each(function () { 46 | checked.push($(this).attr("id")); 47 | }); 48 | downloadRecording.downloadJMX(name.val(), checked, transactions); 49 | }); 50 | 51 | $('#cancel').click(() => { 52 | transactions = null; 53 | $("#json-editor").show(); 54 | $("#domains-select").hide(); 55 | }); 56 | }); 57 | 58 | let checkTransactions = function (transactions) { 59 | let keys = Object.keys(transactions); 60 | keys.forEach(key => { 61 | if (transactions[key].hasOwnProperty("url")) { 62 | transactions[key] = checkRequest(transactions[key]); 63 | } else { 64 | transactions[key] = checkTraffic(transactions[key]); 65 | } 66 | }); 67 | 68 | return transactions; 69 | } 70 | 71 | let checkTraffic = function (traffic) { 72 | let keys = Object.keys(traffic); 73 | keys.forEach(key => { 74 | traffic[key] = checkRequest(traffic[key]); 75 | }); 76 | 77 | return traffic; 78 | } 79 | 80 | let checkRequest = function (request) { 81 | if (request.label && typeof (request.label) !== 'string') { 82 | request.label = String(request.label); 83 | } 84 | if (request.method && typeof (request.method) !== 'string') { 85 | request.method = String(request.method); 86 | } 87 | if (request.url && typeof (request.url) !== 'string') { 88 | request.url = String(request.url); 89 | } 90 | if (request.timestamp && typeof (request.timestamp) !== 'number') { 91 | request.timestamp = 0; 92 | } 93 | if (request.transaction_key && typeof (request.transaction_key) !== 'number') { 94 | request.transaction_key = 0; 95 | } 96 | if (request.headers.length > 1) { 97 | request.headers.forEach(header => { 98 | if (header.name && typeof (header.name) !== 'string') { 99 | header.name = String(header.name); 100 | } 101 | if (header.value && typeof (header.value) !== 'string') { 102 | header.value = String(header.value); 103 | } 104 | }) 105 | } 106 | return request; 107 | } -------------------------------------------------------------------------------- /panel/js/UI/hideshow.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 | // Andy Langton's show/hide/mini-accordion @ http://andylangton.co.uk/jquery-show-hide 19 | 20 | // this tells jquery to run the function below once the DOM is ready 21 | $(document).ready(function() { 22 | 23 | // choose text for the show/hide link - can contain HTML (e.g. an image) 24 | var showText = 'Show'; 25 | var hideText = 'Hide'; 26 | 27 | // initialise the visibility check 28 | var is_visible = false; 29 | 30 | // append show/hide links to the element directly preceding the element with a class of "toggle" 31 | $('.toggle').prev().append(' ' + hideText + ''); 32 | 33 | // hide all of the elements with a class of 'toggle' 34 | $('.toggle').show(); 35 | 36 | // capture clicks on the toggle links 37 | $('a.toggleLink').click(function() { 38 | 39 | // switch visibility 40 | is_visible = !is_visible; 41 | 42 | // change the link text depending on whether the element is shown or hidden 43 | if ($(this).text() == showText) { 44 | $(this).text(hideText); 45 | $(this).parent().next('.toggle').slideDown('slow'); 46 | } else { 47 | $(this).text(showText); 48 | $(this).parent().next('.toggle').slideUp('slow'); 49 | } 50 | 51 | // return false so any link destination is not followed 52 | return false; 53 | 54 | }); 55 | }); 56 | 57 | function mouseOnAndOutTestSuite(event) { 58 | //event.stopPropagation(); 59 | var element = event.target; 60 | while (true) { 61 | if (element == undefined) { 62 | return; 63 | } 64 | if (element.id.includes("suite") && !element.id.includes("menu")) { 65 | break; 66 | } 67 | 68 | element = element.parentNode; 69 | } 70 | 71 | // console.log("element: ", element); 72 | let display = undefined; 73 | if (event.type == "mouseover") { 74 | display = true; 75 | } else if (event.type == "mouseout") { 76 | display = false; 77 | } 78 | setIconDisplay(display, element); 79 | } 80 | 81 | function setIconDisplay(display, element) { 82 | let plus = element.getElementsByClassName("fa fa-download")[0]; 83 | let download = element.getElementsByClassName("fa fa-plus")[0]; 84 | let color = display ? "rgb(156, 155, 155)": "rgb(223, 223, 223)"; 85 | plus.style.color = color; 86 | download.style.color = color; 87 | } 88 | 89 | function mouseOnSuiteTitleIcon(event) { 90 | let tempElement = getMouseActionElement(event.target); 91 | if (tempElement == null) { 92 | return; 93 | } 94 | tempElement.style.color = "rgb(106, 105, 105)"; 95 | } 96 | 97 | function mouseOutSuiteTitleIcon(event) { 98 | let tempElement = getMouseActionElement(event.target); 99 | if (tempElement == null) { 100 | return; 101 | } 102 | tempElement.style.color = "rgb(167, 167, 167)"; 103 | } 104 | 105 | function getMouseActionElement(target) { 106 | let tagName = target.tagName; 107 | if ( tagName == "DIV") { 108 | // NOTE: id will be suite-open or suite-plus 109 | return $("i." + target.id)[0]; 110 | } else if (tagName == "I") { 111 | return target; 112 | } 113 | return null; 114 | } 115 | -------------------------------------------------------------------------------- /panel/js/UI/command_grid_toolbar.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 | $("#command-command").on("input", function(event) { 19 | var temp = getSelectedRecord(); 20 | if (temp) { 21 | var div = getTdRealValueNode(document.getElementById(temp), 0); 22 | // set innerHTML = "" 23 | if (div.childNodes && div.childNodes[0]) { 24 | div.removeChild(div.childNodes[0]); 25 | } 26 | div.appendChild(document.createTextNode(event.target.value)); 27 | 28 | var command_command = event.target.value; 29 | div = getTdShowValueNode(document.getElementById(temp), 0); 30 | if (div.childNodes && div.childNodes[0]) { 31 | div.removeChild(div.childNodes[0]); 32 | } 33 | div.appendChild(document.createTextNode(command_command)); 34 | 35 | // store command grid to testCase 36 | var s_case = getSelectedCase(); 37 | if (s_case) { 38 | sideex_testCase[s_case.id].records = document.getElementById("records-grid").innerHTML; 39 | modifyCaseSuite(); 40 | } 41 | } 42 | }); 43 | 44 | $("#command-target").on("input", function(event) { 45 | var temp = getSelectedRecord(); 46 | if (temp) { 47 | var div = getTdRealValueNode(document.getElementById(temp), 1); 48 | // Check hidden value and target value 49 | if (!(div.childNodes[0].textContent.includes("d-XPath") && event.target.value.includes("tac"))) { 50 | var real_command_target = event.target.value; 51 | if (real_command_target == "auto-located-by-tac") { 52 | // Real tac value is hidden 53 | var real_tac = getTargetDatalist(document.getElementById(temp)).options[0].text; 54 | if (real_tac == "") real_tac = "auto-located-by-tac"; 55 | real_command_target = real_tac; 56 | } 57 | if (div.childNodes && div.childNodes[0]) { 58 | div.removeChild(div.childNodes[0]); 59 | } 60 | div.appendChild(document.createTextNode(real_command_target)); 61 | 62 | var command_target = event.target.value; 63 | div = getTdShowValueNode(document.getElementById(temp), 1); 64 | if (command_target.includes("tac")) { 65 | command_target = "auto-located-by-tac"; 66 | } 67 | if (div.childNodes && div.childNodes[0]) { 68 | div.removeChild(div.childNodes[0]); 69 | } 70 | div.appendChild(document.createTextNode(command_target)); 71 | let datalist = getTargetDatalist(document.getElementById(temp)); 72 | } 73 | 74 | // store command grid to testCase 75 | var s_case = getSelectedCase(); 76 | if (s_case) { 77 | sideex_testCase[s_case.id].records = document.getElementById("records-grid").innerHTML; 78 | modifyCaseSuite(); 79 | } 80 | } 81 | }); 82 | 83 | $("#command-value").on("input", function(event) { 84 | var temp = getSelectedRecord(); 85 | if (temp) { 86 | var div = getTdRealValueNode(document.getElementById(temp), 2); 87 | // set innerHTML = "" 88 | if (div.childNodes && div.childNodes[0]) { 89 | div.removeChild(div.childNodes[0]); 90 | } 91 | div.appendChild(document.createTextNode(event.target.value)); 92 | 93 | var command_value = event.target.value; 94 | div = getTdShowValueNode(document.getElementById(temp), 2); 95 | if (div.childNodes && div.childNodes[0]) { 96 | div.removeChild(div.childNodes[0]); 97 | } 98 | div.appendChild(document.createTextNode(command_value)); 99 | 100 | // store command grid to testCase 101 | var s_case = getSelectedCase(); 102 | if (s_case) { 103 | sideex_testCase[s_case.id].records = document.getElementById("records-grid").innerHTML; 104 | modifyCaseSuite(); 105 | } 106 | } 107 | }); 108 | -------------------------------------------------------------------------------- /editor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 编辑器 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 133 | 134 | 135 |
136 | 137 |
138 | 编辑器 139 | 140 |
141 | 142 | 143 | 144 | 145 |
146 |
147 |
148 |
149 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /html/transaction-ui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | MeterSphere Record 5 | 6 | 7 | 8 | 158 | 159 | 160 |
161 |
162 |
163 | 164 | 165 | 166 | JMX 167 |
168 | 170 |
171 | 175 |
176 | 177 | 178 | -------------------------------------------------------------------------------- /content/recorder.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 Recorder { 19 | 20 | constructor(window) { 21 | this.window = window; 22 | this.attached = false; 23 | this.locatorBuilders = new LocatorBuilders(window); 24 | this.frameLocation = this.getFrameLocation(); 25 | browser.runtime.sendMessage({ 26 | frameLocation: this.frameLocation 27 | }).catch(function(reason) { 28 | // Failed silently if receiving end does not exist 29 | }); 30 | } 31 | 32 | // This part of code is copyright by Software Freedom Conservancy(SFC) 33 | parseEventKey(eventKey) { 34 | if (eventKey.match(/^C_/)) { 35 | return { eventName: eventKey.substring(2), capture: true }; 36 | } else { 37 | return { eventName: eventKey, capture: false }; 38 | } 39 | } 40 | 41 | // This part of code is copyright by Software Freedom Conservancy(SFC) 42 | attach() { 43 | if (this.attached) { 44 | return; 45 | } 46 | this.attached = true; 47 | this.eventListeners = {}; 48 | var self = this; 49 | for (let eventKey in Recorder.eventHandlers) { 50 | var eventInfo = this.parseEventKey(eventKey); 51 | var eventName = eventInfo.eventName; 52 | var capture = eventInfo.capture; 53 | // create new function so that the variables have new scope. 54 | function register() { 55 | var handlers = Recorder.eventHandlers[eventKey]; 56 | var listener = function(event) { 57 | for (var i = 0; i < handlers.length; i++) { 58 | handlers[i].call(self, event); 59 | } 60 | } 61 | this.window.document.addEventListener(eventName, listener, capture); 62 | this.eventListeners[eventKey] = listener; 63 | } 64 | register.call(this); 65 | } 66 | } 67 | 68 | // This part of code is copyright by Software Freedom Conservancy(SFC) 69 | detach() { 70 | if (!this.attached) { 71 | return; 72 | } 73 | this.attached = false; 74 | for (let eventKey in this.eventListeners) { 75 | var eventInfo = this.parseEventKey(eventKey); 76 | var eventName = eventInfo.eventName; 77 | var capture = eventInfo.capture; 78 | this.window.document.removeEventListener(eventName, this.eventListeners[eventKey], capture); 79 | } 80 | delete this.eventListeners; 81 | } 82 | 83 | getFrameLocation() { 84 | let currentWindow = window; 85 | let currentParentWindow; 86 | let frameLocation = "" 87 | while (currentWindow !== window.top) { 88 | currentParentWindow = currentWindow.parent; 89 | for (let idx = 0; idx < currentParentWindow.frames.length; idx++) 90 | if (currentParentWindow.frames[idx] === currentWindow) { 91 | frameLocation = ":" + idx + frameLocation; 92 | currentWindow = currentParentWindow; 93 | break; 94 | } 95 | } 96 | return frameLocation = "root" + frameLocation; 97 | } 98 | 99 | record(command, target, value, insertBeforeLastCommand, actualFrameLocation) { 100 | let self = this; 101 | browser.runtime.sendMessage({ 102 | command: command, 103 | target: target, 104 | value: value, 105 | insertBeforeLastCommand: insertBeforeLastCommand, 106 | frameLocation: (actualFrameLocation != undefined ) ? actualFrameLocation : this.frameLocation, 107 | }).catch (function(reason) { 108 | // If receiving end does not exist, detach the recorder 109 | self.detach(); 110 | }); 111 | } 112 | } 113 | 114 | Recorder.eventHandlers = {}; 115 | Recorder.addEventHandler = function(handlerName, eventName, handler, options) { 116 | handler.handlerName = handlerName; 117 | if (!options) options = false; 118 | let key = options ? ('C_' + eventName) : eventName; 119 | if (!this.eventHandlers[key]) { 120 | this.eventHandlers[key] = []; 121 | } 122 | this.eventHandlers[key].push(handler); 123 | } 124 | 125 | 126 | // TODO: new by another object 127 | var recorder = new Recorder(window); 128 | 129 | // TODO: move to appropriate file 130 | // show element 131 | function startShowElement(message, sender, sendResponse){ 132 | if (message.showElement) { 133 | result = selenium["doShowElement"](message.targetValue); 134 | return Promise.resolve({result: result}); 135 | } 136 | } 137 | browser.runtime.onMessage.addListener(startShowElement); 138 | -------------------------------------------------------------------------------- /panel/js/lib/colResizable-1.5.min.js: -------------------------------------------------------------------------------- 1 | // colResizable 1.5 - a jQuery plugin by Alvaro Prieto Lauroba http://www.bacubacu.com/colresizable/ 2 | (function($){var d=$(document),h=$("head"),drag=null,tables=[],count=0,ID="id",PX="px",SIGNATURE="JColResizer",FLEX="JCLRFlex",I=parseInt,M=Math,ie=navigator.userAgent.indexOf('Trident/4.0')>0,S;try{S=sessionStorage}catch(e){};h.append("");var init=function(tb,options){var t=$(tb);t.opt=options;if(t.opt.disable)return destroy(t);var id=t.id=t.attr(ID)||SIGNATURE+ count++;t.p=t.opt.postbackSafe;if(!t.is("table")||tables[id]&&!t.opt.partialRefresh)return;t.addClass(SIGNATURE).attr(ID,id).before('
');t.g=[];t.c=[];t.w=t.width();t.gc=t.prev();t.f=t.opt.fixed;if(options.marginLeft)t.gc.css("marginLeft",options.marginLeft);if(options.marginRight)t.gc.css("marginRight",options.marginRight);t.cs=I(ie?tb.cellSpacing||tb.currentStyle.borderSpacing:t.css('border-spacing'))||2;t.b=I(ie?tb.border||tb.currentStyle.borderLeftWidth:t.css('border-left-width'))||1;tables[id]=t;createGrips(t)},destroy=function(t){var id=t.attr(ID),t=tables[id];if(!t||!t.is("table"))return;t.removeClass(SIGNATURE+" "+FLEX).gc.remove();delete tables[id]},createGrips=function(t){var th=t.find(">thead>tr>th,>thead>tr>td");if(!th.length)th=t.find(">tbody>tr:first>th,>tr:first>th,>tbody>tr:first>td, >tr:first>td");th=th.filter(":visible");t.cg=t.find("col");t.ln=th.length;if(t.p&&S&&S[t.id])memento(t,th);th.each(function(i){var c=$(this),g=$(t.gc.append('
')[0].lastChild);g.append(t.opt.gripInnerHtml).append('
');if(i==t.ln-1){g.addClass("JCLRLastGrip");if(t.f)g.html("")};g.bind('touchstart mousedown',onGripMouseDown);g.t=t;g.i=i;g.c=c;c.w=c.width();t.g.push(g);t.c.push(c);c.width(c.w).removeAttr("width");g.data(SIGNATURE,{i:i,t:t.attr(ID),last:i==t.ln-1})});t.cg.removeAttr("width");syncGrips(t);t.find('td, th').not(th).not('table th, table td').each(function(){$(this).removeAttr('width')});if(!t.f)t.removeAttr('width').addClass(FLEX)},memento=function(t,th){var w,m=0,i=0,aux=[],tw;if(th){t.cg.removeAttr("width");if(t.opt.flush){S[t.id]="";return};w=S[t.id].split(";");tw=w[t.ln+1];if(!t.f&&tw)t.width(tw);for(;i*{cursor:"+t.opt.dragCursor+"!important}");g.addClass(t.opt.draggingClass);drag=g;if(t.c[o.i].l)for(var i=0,c;i