├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── CHANGELOG.md ├── Gruntfile.js ├── LICENSE ├── Notes.txt ├── README.md ├── images ├── icon.png └── icon.svg ├── manifest.json ├── package-lock.json ├── package.json └── scripts ├── background └── main.js ├── content └── contentInject.js ├── injected ├── pageInject.css └── pageInject.js └── popup ├── .eslintrc.json ├── Component.js ├── controller ├── App.controller.js ├── BaseController.js ├── Mockdata.controller.js ├── Settings.controller.js ├── Start.controller.js ├── TestDetails.controller.js └── TestStep.controller.js ├── css └── additional.css ├── faker.js ├── fragment ├── ComponentDialog.fragment.xml ├── ConnectionEstablishingDialog.fragment.xml ├── PopoverActionSettings.fragment.xml ├── RecordDialog.fragment.xml ├── SelectAssociationGeneration.fragment.xml ├── SelectComponent.fragment.xml ├── SelectFaker.fragment.xml ├── SelectFixedValue.fragment.xml ├── SelectFormula.fragment.xml ├── SelectMultipleValues.fragment.xml ├── SelectURL.fragment.xml └── ValueSelectionActionSheet.fragment.xml ├── i18n └── i18n.properties ├── index.html ├── libs ├── FileSaver.min.js └── jszip.min.js ├── model ├── ChromeStorage.js ├── CodeHelper.js ├── Connection.js ├── ConnectionMessages.js ├── GlobalSettings.js ├── Mockserver.js ├── RecordController.js ├── Utils.js └── code-generation │ ├── NaturalCodeStrategy.js │ ├── OPA5CodeStrategy.js │ ├── TestCafeCodeStrategy.js │ ├── UIVeri5CodeStrategy.js │ └── opa5 │ ├── CommonBuilder.js │ ├── ItemBindingMatcherBuilder.js │ ├── PageBuilder.js │ ├── ParentMatcherBuilder.js │ └── ViewBuilder.js ├── util ├── ItemConstants.js └── StringBuilder.js └── view ├── App.view.xml ├── Mockdata.view.xml ├── Settings.view.xml ├── Start.view.xml ├── TestDetails.view.xml └── TestStep.view.xml /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | ui5/ 3 | faker.js 4 | dist/ 5 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 8 4 | }, 5 | "env": { 6 | "browser": true, 7 | "node" : true, 8 | "es6" : true, 9 | "webextensions": true 10 | }, 11 | "globals": { 12 | "sap" : true 13 | }, 14 | "rules": { 15 | "brace-style" : ["error", "1tbs", { "allowSingleLine": true }], 16 | "consistent-this" : "error", 17 | "no-div-regex" : "error", 18 | "no-floating-decimal" : "error", 19 | "no-self-compare" : "error", 20 | "no-mixed-spaces-and-tabs" : ["error", "smart-tabs"], 21 | "no-nested-ternary" : "error", 22 | "no-unused-vars" : ["error", {"vars":"all", "args":"none"}], 23 | "radix" : "error", 24 | "wrap-iife" : ["error", "any"], 25 | "camelcase" : "warn", 26 | "consistent-return" : "warn", 27 | "max-nested-callbacks" : ["warn", 4], 28 | "new-cap" : "warn", 29 | "no-extra-boolean-cast" : "warn", 30 | "no-lonely-if" : "warn", 31 | "no-new" : "warn", 32 | "no-new-wrappers" : "warn", 33 | "no-redeclare" : "warn", 34 | "no-unused-expressions" : "warn", 35 | "no-use-before-define" : ["warn", "nofunc"], 36 | "no-warning-comments" : 0, 37 | "strict" : ["error", "function"], 38 | "valid-jsdoc" : ["warn", { "requireReturn" : false }], 39 | "default-case" : "error", 40 | "no-trailing-spaces" : "warn", 41 | "block-scoped-var" : "warn", 42 | "comma-dangle" : "error", 43 | "no-cond-assign" : "error", 44 | "no-console" : "error", 45 | "no-constant-condition" : "error", 46 | "no-control-regex" : "error", 47 | "no-debugger" : "error", 48 | "no-dupe-args" : "error", 49 | "no-dupe-keys" : "error", 50 | "no-duplicate-case" : "error", 51 | "no-empty" : "error", 52 | "no-empty-character-class" : "error", 53 | "no-ex-assign" : "error", 54 | "no-extra-parens" : ["error", "functions"], 55 | "no-extra-semi" : "error", 56 | "no-func-assign" : "error", 57 | "no-inner-declarations" : "error", 58 | "no-invalid-regexp" : "error", 59 | "no-irregular-whitespace" : "error", 60 | "no-obj-calls" : "error", 61 | "no-regex-spaces" : "error", 62 | "no-sparse-arrays" : "error", 63 | "no-unreachable" : "error", 64 | "no-unsafe-negation" : "error", 65 | "use-isnan" : "error", 66 | "valid-typeof" : "error", 67 | 68 | "accessor-pairs" : "error", 69 | "curly" : "error", 70 | "no-alert" : "error", 71 | "no-caller" : "error", 72 | "no-eval" : "error", 73 | "no-extend-native" : "error", 74 | "no-extra-bind" : "error", 75 | "no-fallthrough" : "error", 76 | "no-implied-eval" : "error", 77 | "no-iterator" : "error", 78 | "no-labels" : "error", 79 | "no-lone-blocks" : "error", 80 | "no-loop-func" : "error", 81 | "no-global-assign" : "error", 82 | "no-new-func" : "error", 83 | "no-octal" : "error", 84 | "no-octal-escape" : "error", 85 | "no-proto" : "error", 86 | "no-return-assign" : "error", 87 | "no-script-url" : "error", 88 | "no-sequences" : "error", 89 | "no-void" : "error", 90 | "no-with" : "error", 91 | "yoda" : "error", 92 | 93 | "linebreak-style" : ["warn", "unix"], 94 | "no-catch-shadow" : "warn", 95 | "no-label-var" : "error", 96 | "no-delete-var" : "error", 97 | "no-shadow-restricted-names": "error", 98 | "no-undef-init" : "error", 99 | "no-undef" : "error", 100 | 101 | "new-parens" : "error", 102 | "no-array-constructor" : "error", 103 | "no-new-object" : "error", 104 | "func-call-spacing" : "error", 105 | "quote-props" : ["error", "as-needed", { "keywords": true, "unnecessary": false }], 106 | "semi" : "error", 107 | "semi-spacing" : "warn", 108 | "keyword-spacing" : "error", 109 | "space-infix-ops" : "error", 110 | "space-unary-ops" : "error" 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | ui5/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # next.js build output 62 | .next 63 | 64 | .idea 65 | .vscode 66 | 67 | *.zip 68 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # UI5 Test Recorder – Changelog 2 | 3 | 4 | ## 0.6.0 5 | 6 | For this version, we performed an extensive refactoring in the context of Issue [#10](https://github.com/msg-systems/ui5-testrecorder/issues/10), while we did not stop there. 7 | Due to the extent of the refactoring, only the most important user-visible changes are listed below. 8 | For further details, please see PR [#36](https://github.com/msg-systems/ui5-testrecorder/pull/36). 9 | 10 | ### Added 11 | - Actually execute asserts during replay (7069fcc) 12 | - Actually execute support assistant during replay (fe4d04c) 13 | - Gather and show messages during replay in a MessagePopover (b742c11, 4e239fd) 14 | - The page under test is now locked when it is not to be accessed (0232cc6) 15 | - Further user feedback is shown when extension or page disconnects/reloads (e.g., 17f7922, ba7a716, 7b35702, 9bd5167, and 8b102cd) 16 | - Add changelog file [CHANGELOG.md](CHANGELOG.md) (see Issue [#33](https://github.com/msg-systems/ui5-testrecorder/issues/33)) 17 | - Correct catching of ``-clicks (see Issue [#44](https://github.com/msg-systems/ui5-testrecorder/issues/44) and PR [#52](https://github.com/msg-systems/ui5-testrecorder/pull/52)) 18 | 19 | ### Changed/Improved 20 | - Page injection and connection handling now uses WebExtension functionality, which improves stability (see Issue [#10](https://github.com/msg-systems/ui5-testrecorder/issues/10)) 21 | - Speed of identification of many similar controls has been improved significantly (see Issue [#45](https://github.com/msg-systems/ui5-testrecorder/issues/45), PR [#50](https://github.com/msg-systems/ui5-testrecorder/pull/50) and [#53](https://github.com/msg-systems/ui5-testrecorder/pull/53)) 22 | - Integrate binding parts into test-step attribute filters by displaying fully-qualified binding paths (see Issue [#61](https://github.com/msg-systems/ui5-testrecorder/issues/61)) 23 | - Distinguish "normal" bindings and I18N bindings in OPA5 code generation (see Issues [#61](https://github.com/msg-systems/ui5-testrecorder/issues/61), [#62](https://github.com/msg-systems/ui5-testrecorder/issues/62), and [#68](https://github.com/msg-systems/ui5-testrecorder/issues/68)) 24 | - Replay can be stopped (69fcb66) 25 | - Use a single button 'Replay next step' during manual replay 26 | - Disable test-step handling during replay (8283ae9) 27 | - Refactor README file [README.md](README.md) (see Issue [#34](https://github.com/msg-systems/ui5-testrecorder/issues/34)) 28 | - Refactored the OPA5 code generation due to serveral issues, see PR [#70](https://github.com/msg-systems/ui5-testrecorder/pull/70) and commit (25733a9). 29 | 30 | ### Fixed 31 | - Fix potential page injection problem with item properties (072a04e) 32 | - Fix highlighting of selected element in page under test (1db2b70) 33 | - Fix potential security vulnerabilities in package dependencies and update dependencies 34 | - Reload existing tab before injection to circumvent problems with generated IDs (5dd5bed) 35 | 36 | 37 | ## 0.5.2 38 | 39 | ### Added 40 | - Automatic closing of old tabs on replay 41 | - Offline view of single test steps 42 | 43 | ### Fixed 44 | - Removed recurring calls regarding successful page injection that broke the replay option 45 | - Fixed the controller 'TestDetails' for better replay 46 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function (grunt) { 2 | "use strict"; 3 | grunt.initConfig({ 4 | zip: { 5 | 'release.zip': [ 6 | 'scripts/**/*.*', 7 | 'images/**/*.*', 8 | 'ui5/**/*.*', 9 | 'CHANGELOG.md', 10 | 'README.md', 11 | 'LICENSE', 12 | 'manifest.json' 13 | ] 14 | } 15 | }); 16 | 17 | grunt.loadNpmTasks('grunt-zip'); 18 | }; -------------------------------------------------------------------------------- /Notes.txt: -------------------------------------------------------------------------------- 1 | Vorschlag Timo: 2 | - sap.ui.test.RecordReplay.findControlSelectorByDOMElement({domElement:document.getElementById("__identifier0-container-cart---welcomeView--promotedRow-1-link")}) 3 | 4 | Wichtige Issues: 5 | - https://github.com/SAP/ui5-uiveri5/issues/7 6 | - https://github.com/SAP/ui5-uiveri5/issues/27 7 | 8 | Links UI5 Gruppe: 9 | - https://chrome.google.com/webstore/devconsole/g09391382422910291111/hcpkckcanianjcbiigbklddcpfiljmhj/edit/listing?hl=de 10 | - https://groups.google.com/forum/#!managemembers/ui5-testrecorder 11 | 12 | Packaging: 13 | - scripts 14 | - bilder 15 | - ui5 16 | - README.md 17 | - CHANGELOG.md 18 | - manifest.json 19 | - LICENSE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Logo of UI5 test recorder](images/icon.png)](http://msg-systems.github.io/ui5-testrecorder/) 2 | 3 | # UI5 Test Recorder 4 | 5 | The *UI5 Test Recorder* is intended to use for your daily testing of UI5 applications. 6 | The tool enables efficient test automation for SAP UI5 and OpenUI5 applications, by enabling the user to record test scenarios with only simple tools. 7 | While recording, the tool supports you in setting the perfect combination of unique attributes to allow a stable and reproducible test execution. 8 | 9 | UI5 test recorder is able to generate the test code either for integration tests with [OPA5](https://sap.github.io/openui5-docs/#/Integration_Testing_with_One_Page_Acceptance_Tests_%28OPA5%29_2696ab5) or end-to-end tests with [UIVeri5](https://github.com/SAP/ui5-uiveri5) or [TestCafé](https://devexpress.github.io/testcafe/). 10 | 11 | ## Documentation 12 | 13 | There are several points of interest for users: 14 | 15 | - The official website of the UI5 test recorder is http://msg-systems.github.io/ui5-testrecorder/. 16 | - The official repository is located at GitHub: http://github.com/msg-systems/ui5-testrecorder/. 17 | - Usage documentation can be found [here](http://msg-systems.github.io/ui5-testrecorder/docs/documentation.html). 18 | - The changelog is documented in the file [CHANGELOG.md](CHANGELOG.md). 19 | 20 | ## Install 21 | 22 | The UI5 test recorder is [available through the Chrome Web Store](https://chrome.google.com/webstore/detail/hcpkckcanianjcbiigbklddcpfiljmhj). 23 | 24 | *Note*: Right now, only Chrome is supported, further browsers are under investigation. 25 | 26 | ### Manually install development version 27 | 28 | If you want to get the latest version that is not published, you can perform the following steps: 29 | 30 | - Checkout the repository at either the `master` or `dev` branch. 31 | - Add an folder `ui5/` directly inside the repository folder created during checkout. 32 | - [Download the latest OpenUI5 runtime](https://openui5.org/releases/). 33 | - Extract the downloaded archive into the folder `ui5/` created before. 34 | - Load the UI5 test recorder (i.e., the checked-out repository folder) as an *unpacked extension* inside Chrome. (See the Chrome developer guide [on how to do that](https://developer.chrome.com/extensions/getstarted#manifest).) 35 | 36 | > In the future, we will use a self-contained build provided by [ui5-tooling](https://sap.github.io/ui5-tooling/). 37 | > This will simplify the development workflow significantly. 38 | 39 | ## Test apps 40 | 41 | Testing is important, testing – and using! – the UI5 test recorder even more. 42 | You can test the UI5 test recorder with every app provided with [the OpenUI5 Samples](https://openui5.hana.ondemand.com/#/controls). 43 | 44 | You can also use our [presentation from the UI5con 2019](https://msg-systems.github.io/ui5-testrecorder/presentation2019/index.html). 45 | Additionally, we provide [a corresponding download package](https://msg-systems.github.io/ui5-testrecorder/downloads/presentation2019.7z) for offline usage. 46 | 47 | ## Contributing 48 | 49 | > The contribution guide is still under construction. For now, please just open a pull request against the branch `dev` or open an issue. 50 | 51 | ## License 52 | 53 | The UI5 test recorder is licensed under the Apache License 2.0. 54 | See the file [LICENSE](https://github.com/msg-systems/ui5-testrecorder/blob/master/LICENSE) for more details. 55 | -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/msg-systems/ui5-testrecorder/a2dca1dcfd7789b37a760056ea8baa8e10abc307/images/icon.png -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UI5 Test Recorder", 3 | "short_name": "UI5 Test Rec.", 4 | "description": "A point-and-click adventure for your UI5 tests", 5 | "version": "0.6.0", 6 | "manifest_version": 2, 7 | "homepage_url": "https://github.com/msg-systems/ui5-testrecorder", 8 | "content_security_policy": "script-src 'self' 'unsafe-eval' 'sha256-8Mp2/jkojZWl2xhw6+1lp6xEFaLrnyVxpgGcjuYMKRY=' 'sha256-BM8H9hptGGtVA+992JDuvvUrSAiAMOLPGWGiB70GENk=' 'sha256-Jpaf32FjMKHIAM5gkfYL1qpCBBGpWcKKjS2A66bWCk4=' 'sha256-TK+TTfBbieZ5rYQdFrsU8HNGwnTqBTooAU9v/lJp39Q=' 'sha256-zPKms+/h3oggO9pCLf+1CkxzIIOYNGips087mj1KYv8=' 'sha256-8RfS6hNqDEekagd3sDtTD2fBlhJVAvNagaDDmspNjyk=' 'sha256-inqT2qJb1+B9z/Y5pHA/IbOB6OQr/raMK/kKZoP3NOw=' 'sha256-inqT2qJb1+B9z/Y5pHA/IbOB6OQr/raMK/kKZoP3NOw='; object-src 'self'", 9 | "background": { 10 | "persistence": false, 11 | "scripts": [ 12 | "/scripts/background/main.js" 13 | ] 14 | }, 15 | "web_accessible_resources": [ 16 | "/scripts/injected/*.js", 17 | "/scripts/injected/*.xml", 18 | "/scripts/injected/*.css" 19 | ], 20 | "browser_action": { 21 | "default_title": "UI5 Test Recorder", 22 | "default_icon": "images/icon.png" 23 | }, 24 | "icons": { 25 | "16": "images/icon.png", 26 | "48": "images/icon.png", 27 | "128": "images/icon.png" 28 | }, 29 | "optional_permissions": [ 30 | "tabs", 31 | "https://*/*", 32 | "http://*/*" 33 | ], 34 | "permissions": [ 35 | "activeTab", 36 | "contextMenus", 37 | "storage" 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui5-testrecorder", 3 | "description": "A point-and-click adventure for your UI5 tests", 4 | "version": "0.6.0", 5 | "license": "Apache-2.0", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/msg-systems/ui5-testrecorder.git" 9 | }, 10 | "devDependencies": { 11 | "eslint": "*", 12 | "showdown": "*" 13 | }, 14 | "dependencies": { 15 | "grunt": "^1.1.0", 16 | "grunt-zip": "^0.18.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/background/main.js: -------------------------------------------------------------------------------- 1 | var bInitialized = false; 2 | var bNextImmediateStart = false; 3 | 4 | /** 5 | * Open /scripts/popup/index.html in new popup-type window. 6 | */ 7 | chrome.browserAction.onClicked.addListener(function (tab) { 8 | "use strict"; 9 | 10 | // create new window in popup type and focus it immediately 11 | chrome.windows.create({ 12 | url: chrome.extension.getURL('/scripts/popup/index.html'), 13 | type: 'popup', 14 | focused: true 15 | }, function (fnWindow) { 16 | 17 | if (bInitialized === false) { 18 | 19 | // new window is now initialized 20 | bInitialized = true; 21 | 22 | // send handshake message with window ID and 'bNextImmediateStart' 23 | chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { 24 | if (message.type === "handshake-get-window-id") { 25 | sendResponse({ 26 | "type": "handshake-send-window-id", 27 | "windowId": fnWindow.id, 28 | "startImmediately": bNextImmediateStart 29 | }); 30 | } 31 | }); 32 | } 33 | 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /scripts/content/contentInject.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | // prefix for any created DOM-node tags 5 | const TAG_ID_PREFIX = "UI5TR-functions"; 6 | 7 | /** 8 | * ConnectionProxy class to handle any page–extension messaging. 9 | * 10 | * This class is singleton to only allow one port per page. 11 | * 12 | * Messages between the proxy and the extension are handled using a {@link chrome.runtime.Port}. 13 | * Messages between the proxy and the page are handled using a {@link window.message} and corresponding listeners. 14 | */ 15 | class ConnectionProxy { 16 | 17 | /** 18 | * Obtain the singleton instance of this class. 19 | * 20 | * @returns {ConnectionProxy} the singleton instance 21 | */ 22 | static getInstance() { 23 | if (!ConnectionProxy._oInstance) { 24 | ConnectionProxy._oInstance = new ConnectionProxy(); 25 | } 26 | 27 | return ConnectionProxy._oInstance; 28 | } 29 | 30 | /** 31 | * Generate a unique injection ID. 32 | * 33 | * @returns {string} the generated injection ID 34 | */ 35 | static _generateInjectID() { 36 | console.log('- Generating injection id'); 37 | var sInjectId = 'xxxyxxxy'.replace(/[xy]/g, function (c) { 38 | var r = Math.random() * 16 | 0, 39 | v = c == 'x' ? r : (r & 0x3 | 0x8); 40 | return v.toString(16); 41 | }); 42 | 43 | return sInjectId; 44 | } 45 | 46 | /** 47 | * Start connection by opening ports and application of listeners. 48 | */ 49 | start() { 50 | // open port for extension messaging 51 | this._openPort(); 52 | // add listener for incoming and outgoing messages 53 | this._port.onMessage.addListener(this._handleMessagesFromExtension.bind(this)); 54 | this._port.onDisconnect.addListener(this._handleExtensionDisconnect.bind(this)); 55 | this._handleMessagesFromPageBind = this._handleMessagesFromPage.bind(this); 56 | window.addEventListener("message", this._handleMessagesFromPageBind); 57 | 58 | } 59 | 60 | /** 61 | * Open the port for extension messaging. 62 | */ 63 | _openPort() { 64 | console.log("- Create connection to extension"); 65 | this._port = chrome.runtime.connect({ 66 | name: ConnectionProxy._generateInjectID() + "-UI5TR" 67 | }); 68 | } 69 | 70 | /** 71 | * Send message to extension. 72 | * 73 | * @param {string} sType the message type/label 74 | * @param {object} oCarrierData the data of the message 75 | */ 76 | sendToExtension(sType, oCarrierData) { 77 | this._port.postMessage({ 78 | type: sType, 79 | data: oCarrierData 80 | }); 81 | } 82 | 83 | /** 84 | * Listener function for messages from the extension, 85 | * to be forwarded to the page. 86 | * 87 | * @param {object} oMessage the message information from the extension to the page. 88 | */ 89 | _handleMessagesFromExtension(oMessage) { 90 | this.sendToPage(oMessage.action, oMessage.data, oMessage.messageID); 91 | } 92 | 93 | /** 94 | * Send message to page. 95 | * 96 | * @param {string} sAction the action to perform on page side 97 | * @param {object} oCarrierData the data used to perform the action 98 | * @param {integer} iMessageID the message's ID for callback identification 99 | */ 100 | sendToPage(sAction, oCarrierData, iMessageID = null) { 101 | window.postMessage({ 102 | origin: "FROM_EXTENSION", 103 | messageID: iMessageID, 104 | type: sAction, 105 | data: oCarrierData 106 | }); 107 | } 108 | 109 | /** 110 | * Event handler for page communication to the extension 111 | * 112 | * @param {object} oMessage the message information from the extension to the page 113 | */ 114 | _handleMessagesFromPage(oMessage) { 115 | if (oMessage.source !== window) { 116 | return; 117 | } 118 | 119 | // only handle if messages are from the page 120 | if (oMessage.data.origin && (oMessage.data.origin === "FROM_PAGE")) { 121 | console.debug("Received message from page."); 122 | 123 | // handle messages by their type 124 | switch (oMessage.data.type) { 125 | 126 | // page injection is completed 127 | case "injectDone": 128 | 129 | var data = {}; 130 | 131 | // UI5 found on page 132 | if (oMessage.data.data.status === "success") { 133 | console.log('Finished setup, inform extension about success!'); 134 | 135 | // inject CSS 136 | // eslint-disable-next-line no-use-before-define 137 | PageInjector.injectCSS(); 138 | 139 | // return UI5 information 140 | data = { 141 | status: oMessage.data.data.status, 142 | version: oMessage.data.data.version, 143 | name: oMessage.data.data.name 144 | }; 145 | } /* *no* UI5 found on page */ else { 146 | console.log('Finished setup, inform extension about failure!'); 147 | 148 | // remove injected JS 149 | // eslint-disable-next-line no-use-before-define 150 | PageInjector.removeJS(); 151 | 152 | data = { 153 | status: oMessage.data.data.status, 154 | message: "No UI5 found in page, try to re-inject or make clear the page contains a UI5 version." 155 | }; 156 | } 157 | 158 | // send error message to extension 159 | this.sendToExtension( 160 | "injection", 161 | { 162 | reason: "injectDone", 163 | data: data 164 | } 165 | ); 166 | 167 | // disconnect the port if injection was aborted 168 | if (oMessage.data.data.status === "error") { 169 | this._port.disconnect(); 170 | // remove listener 171 | window.removeEventListener("message", this._handleMessagesFromPageBind); 172 | } 173 | 174 | break; 175 | 176 | // by default, only relay messages to extension 177 | default: 178 | 179 | this.sendToExtension( 180 | "information", 181 | { 182 | messageID: event.data.messageID, 183 | reason: oMessage.data.type, 184 | data: oMessage.data.data 185 | } 186 | ); 187 | } // switch (oMessage.data.type) 188 | } // if (oMessage.data.origin && (oMessage.data.origin === "FROM_PAGE")) 189 | } 190 | 191 | /** 192 | * Handle the case that the port to the extension has been disconnected. Notify the page. 193 | * 194 | * @param {chrome.runtime.Port} oPort the disconnected Port instance 195 | */ 196 | _handleExtensionDisconnect(oPort) { 197 | this.sendToPage("disconnect", {}); 198 | } 199 | } 200 | 201 | /** 202 | * PageInjector class to statically handle page injection. 203 | * 204 | * This class only contains static methods to handle injections. 205 | */ 206 | class PageInjector { 207 | 208 | /** 209 | * Inject the JS part of the page injection (i.e., {@file /scripts/injected/pageInject.js}). 210 | */ 211 | static injectJS() { 212 | console.log("- Inject page script and wait until UI5 is loaded"); 213 | var script = document.createElement('script'); 214 | script.id = TAG_ID_PREFIX + "-js"; 215 | script.src = chrome.extension.getURL('/scripts/injected/pageInject.js'); 216 | script.defer = "defer"; 217 | var head = document.head; 218 | head.appendChild(script); 219 | } 220 | 221 | static removeJS() { 222 | var oJSTag = document.getElementById(TAG_ID_PREFIX + "-js"); 223 | if (oJSTag) { 224 | oJSTag.remove(); 225 | } 226 | } 227 | 228 | /** 229 | * Inject the CSS part of the page injection (i.e., {@file /scripts/injected/pageInject.css}). 230 | */ 231 | static injectCSS() { 232 | //add the UI5-Testrecorder formatting 233 | var link = document.createElement('link'); 234 | link.id = TAG_ID_PREFIX + "-css"; 235 | link.rel = 'stylesheet'; 236 | link.type = 'text/css'; 237 | link.href = chrome.extension.getURL('/scripts/injected/pageInject.css'); 238 | link.media = 'all'; 239 | document.head.appendChild(link); 240 | } 241 | } 242 | 243 | // open connections and inject JS 244 | ConnectionProxy.getInstance().start(); 245 | PageInjector.injectJS(); 246 | 247 | }()); 248 | -------------------------------------------------------------------------------- /scripts/injected/pageInject.css: -------------------------------------------------------------------------------- 1 | .UI5TR_ElementHover, 2 | .UI5TR_ElementHover * { 3 | background: rgb(193, 137, 156) !important; 4 | } 5 | 6 | .UI5TR_ControlFound, 7 | .UI5TR_ControlFound * { 8 | background: rgb(113, 148, 175) !important; 9 | } 10 | 11 | #UI5TR_BusyDialog-Dialog .sapUiLocalBusyIndicatorAnimation > div::before { 12 | background: #a01441; 13 | } 14 | -------------------------------------------------------------------------------- /scripts/popup/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "webextensions": true 6 | }, 7 | "globals": { 8 | "sap": true, 9 | "jQuery": true 10 | }, 11 | "rules": { 12 | "require-jsdoc": [ 13 | "error", 14 | { 15 | "require": { 16 | "FunctionDeclaration": true, 17 | "MethodDefinition": true, 18 | "ClassDeclaration": false, 19 | "ArrowFunctionExpression": false, 20 | "FunctionExpression": true 21 | } 22 | } 23 | ], 24 | "no-var": 0 25 | } 26 | } -------------------------------------------------------------------------------- /scripts/popup/Component.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([ 2 | 'sap/ui/core/UIComponent' 3 | ], function (UIComponent) { 4 | 'use strict'; 5 | 6 | return UIComponent.extend('com.ui5.testing.Component', { 7 | metadata: { 8 | manifest: { 9 | _version: "1.0.0", 10 | "sap.app": { 11 | _version: "1.0.0", 12 | id: "com.ui5.testing", 13 | type: "application", 14 | i18n: "i18n/i18n.properties", 15 | title: "{{appTitle}}", 16 | description: "{{appDescription}}", 17 | applicationVersion: { 18 | version: "1.0.0" 19 | }, 20 | dataSources: { 21 | } 22 | }, 23 | "sap.ui": { 24 | _version: "1.3.0", 25 | technology: "UI5", 26 | icons: { 27 | icon: "sap-icon://detail-view" 28 | }, 29 | deviceTypes: { 30 | desktop: true, 31 | tablet: true, 32 | phone: true 33 | }, 34 | supportedThemes: [ 35 | "sap_belize_plus" 36 | ], 37 | fullWidth: true 38 | }, 39 | "sap.ui5": { 40 | _version: "1.2.0", 41 | handleValidation: true, 42 | rootView: { 43 | viewName: "com.ui5.testing.view.App", 44 | type: "XML", 45 | id: "app" 46 | }, 47 | dependencies: { 48 | minUI5Version: "1.48.0", 49 | libs: { 50 | "sap.ui.core": {}, 51 | "sap.m": {}, 52 | "sap.ui.layout": {} 53 | } 54 | }, 55 | contentDensities: { 56 | compact: true, 57 | cozy: true 58 | }, 59 | models: { 60 | i18n: { 61 | type: "sap.ui.model.resource.ResourceModel", 62 | settings: { 63 | bundleName: "com.ui5.testing.i18n.i18n" 64 | } 65 | } 66 | }, 67 | routing: { 68 | config: { 69 | routerClass: "sap.m.routing.Router", 70 | viewType: "XML", 71 | viewPath: "com.ui5.testing.view", 72 | controlId: "idAppControl", 73 | controlAggregation: "pages", 74 | bypassed: { 75 | target: [ 76 | "TestStep" 77 | ] 78 | }, 79 | async: true 80 | }, 81 | routes: [ 82 | { 83 | pattern: "/TestDetails/{TestId}/elementCreate/{ElementId}/{ui5Version}", 84 | name: "elementCreate", 85 | target: "testElement" 86 | }, 87 | { 88 | pattern: "/TestDetails/{TestId}/elementCreateQuick/{ElementId}/{ui5Version}", 89 | name: "elementCreateQuick", 90 | target: "testElement" 91 | }, 92 | { 93 | pattern: "/elementDisplay/{TestId}/{ElementId}/{ui5Version}", 94 | name: "elementDisplay", 95 | target: "testElement" 96 | }, 97 | { 98 | pattern: "/mockdata", 99 | name: "mockdata", 100 | target: "mockdata" 101 | }, 102 | { 103 | pattern: "/TestDetails/{TestId}", 104 | name: "TestDetails", 105 | target: "TestDetails" 106 | }, 107 | { 108 | pattern: "/TestDetailsCreate", 109 | name: "TestDetailsCreate", 110 | target: "TestDetails" 111 | }, 112 | { 113 | pattern: "/TestDetailsCreateQuick", 114 | name: "TestDetailsCreateQuick", 115 | target: "TestDetails" 116 | }, 117 | { 118 | pattern: "", 119 | name: "start", 120 | target: "start" 121 | }, 122 | { 123 | pattern: "/settings", 124 | name: "settings", 125 | target: "settings" 126 | } 127 | ], 128 | targets: { 129 | mockdata: { 130 | viewName: "Mockdata", 131 | viewLevel: 1, 132 | viewId: "Mockdata", 133 | controlAggregation: "pages" 134 | }, 135 | testElement: { 136 | viewName: "TestStep", 137 | viewLevel: 1, 138 | viewId: "TestStep", 139 | controlAggregation: "pages" 140 | }, 141 | TestDetails: { 142 | viewName: "TestDetails", 143 | viewLevel: 1, 144 | viewId: "TestDetails", 145 | controlAggregation: "pages" 146 | }, 147 | start: { 148 | viewName: "Start", 149 | viewLevel: 1, 150 | viewId: "start", 151 | controlAggregation: "pages" 152 | }, 153 | settings: { 154 | viewName: "Settings", 155 | viewLevel: 1, 156 | viewId: "settings", 157 | controlAggregation: "pages" 158 | } 159 | } 160 | }, 161 | resources: { 162 | css: [{ 163 | uri: "css/additional.css" 164 | }] 165 | } 166 | } 167 | } 168 | }, 169 | 170 | init: function () { 171 | UIComponent.prototype.init.apply(this, arguments); 172 | this.getRouter().initialize(); 173 | }, 174 | 175 | destroy: function () { 176 | UIComponent.prototype.destroy.apply(this, arguments); 177 | }, 178 | 179 | getContentDensityClass: function () { 180 | return 'sapUiSizeCompact'; 181 | } 182 | 183 | }); 184 | }); 185 | -------------------------------------------------------------------------------- /scripts/popup/controller/App.controller.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([ 2 | "sap/ui/core/mvc/Controller", 3 | "sap/ui/model/json/JSONModel", 4 | "com/ui5/testing/model/Utils", 5 | "com/ui5/testing/model/GlobalSettings" 6 | ], function (Controller, JSONModel, Utils, GlobalSettings) { 7 | "use strict"; 8 | 9 | return Controller.extend("com.ui5.testing.controller.App", { 10 | onInit: function () { 11 | //If necessary initialize the global model for all test records 12 | if (!this.getOwnerComponent().getModel('records')) { 13 | this.getOwnerComponent().setModel(new JSONModel({}), 'records'); 14 | } 15 | 16 | //setup the global model for constants 17 | if (!this.getOwnerComponent().getModel('constants')) { 18 | this.getOwnerComponent().setModel(new JSONModel(Utils.statics), 'constants'); 19 | } 20 | 21 | //setup the global settings model 22 | if (!this.getOwnerComponent().getModel('settings')) { 23 | this.getOwnerComponent().setModel(GlobalSettings.getModel(), 'settings'); 24 | } 25 | 26 | //jQuery.sap.log.setLevel(jQuery.sap.log.Level.INFO); 27 | } 28 | }); 29 | }); -------------------------------------------------------------------------------- /scripts/popup/controller/BaseController.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([ 2 | 'sap/ui/core/mvc/Controller', 3 | 'sap/ui/core/routing/History' 4 | ], function (Controller, History) { 5 | 'use strict'; 6 | 7 | return Controller.extend('com.ui5.testing.controller.BaseController', { 8 | _oResourceBundle: null, 9 | 10 | onInit: function () { 11 | this._oResourceBundle = this.getResourceBundle(); 12 | }, 13 | 14 | getRouter: function() { 15 | return this.getOwnerComponent().getRouter(); 16 | }, 17 | 18 | getModel: function(sName) { 19 | return this.getView().getModel(sName); 20 | }, 21 | 22 | setModel: function(oModel, sName) { 23 | return this.getView().setModel(oModel, sName); 24 | }, 25 | 26 | getResourceBundle: function() { 27 | return this.getOwnerComponent().getModel('i18n').getResourceBundle(); 28 | }, 29 | 30 | getMessageManager: function() { 31 | return sap.ui.getCore().getMessageManager(); 32 | }, 33 | 34 | onNavBack: function(oEvent) { 35 | history.go(-1); 36 | }, 37 | 38 | _setBusy: function (bBusy, sModelName) { 39 | return new Promise(function(resolve) { 40 | this._setBusyIndicatorImmediate(bBusy, sModelName); 41 | resolve(); 42 | }.bind(this)); 43 | }, 44 | 45 | _setBusyIndicatorImmediate: function (bBusy, sModelName) { 46 | var oViewModel = this.getModel(sModelName ? sModelName : "viewModel"); 47 | var sBusy = "/busy"; 48 | var sDelay = "/delay"; 49 | if (bBusy === true) { 50 | oViewModel.setProperty(sBusy, true); 51 | oViewModel.setProperty(sDelay, 0); 52 | } else { 53 | oViewModel.setProperty(sBusy, false); 54 | oViewModel.setProperty(sDelay, this._iBusyIndicatorDelay); 55 | } 56 | } 57 | }); 58 | }); -------------------------------------------------------------------------------- /scripts/popup/controller/Mockdata.controller.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([ 2 | "com/ui5/testing/controller/BaseController", 3 | "com/ui5/testing/model/RecordController", 4 | "com/ui5/testing/model/Connection", 5 | "com/ui5/testing/model/ConnectionMessages", 6 | "sap/ui/model/json/JSONModel", 7 | "sap/ui/core/Fragment" 8 | ], function ( 9 | BaseController, 10 | RecordController, 11 | Connection, 12 | ConnectionMessages, 13 | JSONModel, 14 | Fragment) { 15 | "use strict"; 16 | 17 | return BaseController.extend("com.ui5.testing.controller.Mockdata", { 18 | 19 | /** 20 | * 21 | */ 22 | onInit: function () { 23 | this.getView().setModel(new JSONModel({}), "mockdataModel"); 24 | this.getRouter().getRoute("mockdata").attachPatternMatched(this._onObjectMatched, this); 25 | }, 26 | 27 | /** 28 | * 29 | */ 30 | _onObjectMatched: function () { 31 | 32 | Fragment.load({ 33 | name: "com.ui5.testing.fragment.ComponentDialog", 34 | controller: this 35 | }).then(function (oWaitDialog) { 36 | this._oWaitingDialog = oWaitDialog; 37 | /* this._oRecordDialog.attachClose(this.onStopRecord, this); */ 38 | var iTimeoutID = setTimeout(function () { 39 | this._oWaitingDialog.open(); 40 | }.bind(this), 1000 /* 1 sec enough? */ ); 41 | ConnectionMessages.rootComponentAvailable(Connection.getInstance()).then(function (oData) { 42 | clearTimeout(iTimeoutID); 43 | this._oWaitingDialog.close(); 44 | debugger; 45 | }.bind(this)); 46 | }.bind(this)); 47 | }, 48 | 49 | /** 50 | * 51 | */ 52 | retrieveODataV2: function () { 53 | ConnectionMessages.getODataV2Models(Connection.getInstance()).then(function (oData) { 54 | debugger; 55 | }); 56 | } 57 | }); 58 | }); -------------------------------------------------------------------------------- /scripts/popup/controller/Settings.controller.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([ 2 | "com/ui5/testing/controller/BaseController", 3 | "com/ui5/testing/model/GlobalSettings", 4 | "com/ui5/testing/model/ChromeStorage", 5 | "sap/m/MessageToast" 6 | ], function (BaseController, GlobalSettings, ChromeStorage, MessageToast) { 7 | "use strict"; 8 | 9 | return BaseController.extend("com.ui5.testing.controller.Settings", { 10 | onInit: function () { 11 | this.getRouter().getRoute("settings").attachPatternMatched(this._onObjectMatched, this); 12 | }, 13 | 14 | _onObjectMatched: function () { 15 | GlobalSettings.load(); 16 | }, 17 | 18 | 19 | onSave : function() { 20 | GlobalSettings.save(); 21 | this.getRouter().navTo("start"); 22 | }, 23 | 24 | onClearSettings : function() { 25 | ChromeStorage.remove({ 26 | key: 'settings' 27 | }); 28 | MessageToast.show("Cleared settings"); 29 | GlobalSettings.load(); 30 | } 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /scripts/popup/controller/Start.controller.js: -------------------------------------------------------------------------------- 1 | sap.ui.define([ 2 | "com/ui5/testing/controller/BaseController", 3 | "sap/ui/model/json/JSONModel", 4 | "sap/ui/core/Fragment", 5 | "com/ui5/testing/model/ChromeStorage", 6 | "com/ui5/testing/model/RecordController", 7 | "com/ui5/testing/model/Utils", 8 | "sap/m/MessageBox", 9 | "sap/m/MessageToast" 10 | ], function (BaseController, JSONModel, Fragment, ChromeStorage, RecordController, Utils, MessageBox, MessageToast) { 11 | "use strict"; 12 | 13 | return BaseController.extend("com.ui5.testing.controller.Start", { 14 | /** 15 | * 16 | */ 17 | onInit: function () { 18 | this._oModel = new JSONModel({ 19 | recording: false, 20 | currentUrl: "" 21 | }); 22 | this.getView().setModel(this._oModel, "viewModel"); 23 | this.getRouter().getRoute("start").attachPatternMatched(this._loadTableItems, this); 24 | }, 25 | 26 | /** 27 | * 28 | */ 29 | ui5Change: function () { 30 | this._getUI5Urls(); 31 | }, 32 | 33 | /** 34 | * 35 | */ 36 | _getTabsForAll: function () { 37 | chrome.tabs.query({ 38 | active: true, 39 | currentWindow: false 40 | }, function (tabs) { 41 | this._oModel.setProperty('/urls', tabs); 42 | }.bind(this)); 43 | }, 44 | 45 | /** 46 | * 47 | */ 48 | _getTabsForUI5: function () { 49 | chrome.tabs.query({ 50 | currentWindow: false 51 | }, function (tabs) { 52 | var aData = []; 53 | for (var i = 0; i < tabs.length; i++) { 54 | if (tabs[i].url && tabs[i].url.indexOf('chrome:') < 0) { 55 | /** 56 | * 57 | */ 58 | function checkUI5() { 59 | var normal = [].slice.call(document.head.getElementsByTagName('script')).filter(function (s) { 60 | return s.src.indexOf('sap-ui-core.js') > -1; 61 | }).length == 1; 62 | var onPremise = [].slice.call(document.head.getElementsByTagName('script')).filter(function (s) { 63 | return s.src.indexOf('/sap/bc/ui5_ui5/') > -1; 64 | }).length >= 1; 65 | 66 | return normal || onPremise; 67 | } 68 | 69 | /** 70 | * 71 | * @param {*} tabId 72 | * @param {*} tabUrl 73 | */ 74 | function callback(tabId, tabUrl) { 75 | return function (results) { 76 | if (chrome.runtime.lastError) { 77 | //console.log(chrome.runtime.lastError.message); 78 | } else if (results[0]) { 79 | this._oModel.getProperty('/urls').push({ 80 | url: tabUrl, 81 | id: tabId 82 | }); 83 | this._oModel.updateBindings(true); 84 | } 85 | }; 86 | } 87 | chrome.tabs.executeScript(tabs[i].id, { 88 | code: '(' + checkUI5 + ')();' 89 | }, callback(tabs[i].id, tabs[i].url).bind(this)); 90 | } 91 | } 92 | if (chrome.runtime.lastError) { 93 | //console.log(chrome.runtime.lastError); 94 | } 95 | this._oModel.setProperty('/urls', aData); 96 | }.bind(this)); 97 | }, 98 | 99 | /** 100 | * 101 | */ 102 | _getUI5Urls: function () { 103 | var bRequestUI5 = this.getView().byId('ui5Switch').getState(); 104 | 105 | if (!bRequestUI5) { 106 | this._getTabsForAll(); 107 | } else { 108 | Utils.requestTabsPermission() 109 | .then( 110 | function () { 111 | this._getTabsForUI5(); 112 | }.bind(this) 113 | ) 114 | .catch( 115 | function () { 116 | MessageToast.show('No permissions granted! Showing only active tabs'); 117 | this._getTabsForAll(); 118 | }.bind(this) 119 | ); 120 | } 121 | }, 122 | 123 | /** 124 | * 125 | */ 126 | /** view events **/ 127 | 128 | /** 129 | * Sets the focus on the given Tab to easily find the correct tab to a given Url 130 | * 131 | * @param (sap.ui.base.Event) oEvent button event giving the correct tab id 132 | */ 133 | onDisplayPage: function (oEvent) { 134 | var vTabId = oEvent.getSource().getBindingContext('viewModel').getObject().id; 135 | chrome.tabs.update(vTabId, { 136 | "active": true 137 | }, function (tab) { 138 | chrome.windows.update(tab.windowId, { 139 | focused: true 140 | }); 141 | }); 142 | }, 143 | 144 | /** 145 | * 146 | */ 147 | onReloadTable: function () { 148 | this._getUI5Urls(); 149 | }, 150 | 151 | /** 152 | * 153 | */ 154 | onStartNewRecording: function (oEvent) { 155 | var iId; 156 | if (oEvent.getSource().getBindingContext('viewModel') && oEvent.getSource().getBindingContext('viewModel').getObject()) { 157 | iId = oEvent.getSource().getBindingContext('viewModel').getObject().id; 158 | } 159 | 160 | this._oConnectionEstablishingDialog.open(); 161 | 162 | RecordController.getInstance().injectScript(iId) 163 | .then((oData) => { 164 | this._oConnectionEstablishingDialog.close(); 165 | MessageToast.show(`Script injected: Page uses ${oData.name} at version ${oData.version}.`); 166 | this.getRouter().navTo("TestDetailsCreate"); 167 | }) 168 | .catch((oData) => { 169 | this._oConnectionEstablishingDialog.close(); 170 | MessageBox.error(oData.message); 171 | }); 172 | }, 173 | 174 | /** 175 | * 176 | */ 177 | onMockserver: function (oEvent) { 178 | var iId; 179 | if (oEvent.getSource().getBindingContext('viewModel') && oEvent.getSource().getBindingContext('viewModel').getObject()) { 180 | iId = oEvent.getSource().getBindingContext('viewModel').getObject().id; 181 | } 182 | 183 | this._oConnectionEstablishingDialog.open(); 184 | 185 | RecordController.getInstance().injectScript(iId).then((oData) => { 186 | this._oConnectionEstablishingDialog.close(); 187 | MessageToast.show(`Script injected: Page uses ${oData.name} at version ${oData.version}.`); 188 | this.getRouter().navTo("mockdata"); 189 | }).catch((oData) => { 190 | this._oConnectionEstablishingDialog.close(); 191 | MessageBox.error(oData.message); 192 | }); 193 | }, 194 | 195 | /** 196 | * 197 | */ 198 | onOpenSettings: function () { 199 | this.getRouter().navTo("settings"); 200 | }, 201 | 202 | /** 203 | * 204 | */ 205 | _loadTableItems: function () { 206 | chrome.permissions.contains({ 207 | permissions: ['tabs'], 208 | origins: ["https://*/*", "http://*/*"] 209 | }, function (result) { 210 | if (result) { 211 | this.getView().byId('ui5Switch').setState(true); 212 | } 213 | this._getUI5Urls(); 214 | }.bind(this)); 215 | ChromeStorage.getRecords({ 216 | path: '/items', 217 | model: this._oModel 218 | }); 219 | }, 220 | 221 | /** 222 | * 223 | */ 224 | onNavigateToTest: function (oEvent) { 225 | this.getRouter().navTo("TestDetails", { 226 | TestId: oEvent.getSource().getBindingContext("viewModel").getObject().uuid 227 | }); 228 | }, 229 | 230 | /** 231 | * 232 | */ 233 | onAfterRendering: function () { 234 | document.getElementById("importOrigHelper").addEventListener("change", function (e) { 235 | var files = e.target.files, 236 | reader = new FileReader(); 237 | var fnImportDone = function (input) { 238 | this._importDone(JSON.parse(input.target.result)); 239 | }.bind(this); 240 | reader.onload = fnImportDone; 241 | reader.readAsText(files[0]); 242 | }.bind(this), false); 243 | 244 | Fragment.load({ 245 | name: "com.ui5.testing.fragment.ConnectionEstablishingDialog", 246 | controller: this 247 | }).then(function (oConnectionEstablishingDialog) { 248 | this._oConnectionEstablishingDialog = oConnectionEstablishingDialog; 249 | }.bind(this)); 250 | }, 251 | 252 | /** 253 | * 254 | */ 255 | _importDone: function (oData) { 256 | oData.test.uuid = Utils.getUUIDv4(); 257 | oData.test.createdAt = new Date().getTime(); 258 | ChromeStorage.saveRecord(oData).then(function () { 259 | ChromeStorage.getRecords({ 260 | path: '/items', 261 | model: this._oModel 262 | }); 263 | }.bind(this)); 264 | }, 265 | 266 | /** 267 | * 268 | */ 269 | onImport: function () { 270 | document.getElementById("importOrigHelper").click(); 271 | } 272 | }); 273 | }); -------------------------------------------------------------------------------- /scripts/popup/css/additional.css: -------------------------------------------------------------------------------- 1 | .btnCircle.btnFloat.sapMBtn.sapMBtnBase { 2 | position: absolute; 3 | bottom: 40px; 4 | right: 40px; 5 | width: 60px !important; 6 | height: 60px !important; 7 | background-color: #398dda; 8 | border-radius: 100%; 9 | margin-left: auto; 10 | margin-top: -30px; 11 | margin-right: 25px; 12 | box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5); 13 | display: flex; 14 | justify-content: center; 15 | align-items: center; 16 | transition: 250ms ease; 17 | } 18 | 19 | .btnCircle.sapMBtn:hover>.sapMBtnTransparent.sapMBtnHoverable { 20 | background-color: transparent; 21 | border-color: transparent; 22 | } 23 | 24 | .btnCircle.btnFloat.sapMBtn.sapMBtnBase>.sapMBtnHoverable.sapMBtnIconFirst.sapMBtnInner.sapMBtnTransparent.sapMFocusable>.sapMBtnCustomIcon.sapMBtnIcon.sapMBtnIconLeft.sapUiIcon.sapUiIconMirrorInRTL { 25 | color: white; 26 | /* width: 30px; */ 27 | /* height: 30px; */ 28 | } 29 | 30 | .sapUiLocalBusyIndicatorAnimation>div::before, 31 | .sapContrastPlus .sapUiLocalBusyIndicatorAnimation>div::before { 32 | background: #a01441; 33 | } 34 | 35 | .sapUiLocalBusyIndicatorAnimation>div::after, 36 | .sapContrastPlus .sapUiLocalBusyIndicatorAnimation>div::after { 37 | box-shadow: inset 0 0 2px 1px #797979; 38 | } 39 | 40 | .attributeLine div:first-child { 41 | margin-left: 0.3rem; 42 | margin-right: auto; 43 | margin-top: auto; 44 | margin-bottom: auto; 45 | } 46 | 47 | .attributeLine div:nth-child(2) { 48 | margin-right: 1rem; 49 | margin-left: auto; 50 | } -------------------------------------------------------------------------------- /scripts/popup/fragment/ComponentDialog.fragment.xml: -------------------------------------------------------------------------------- 1 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /scripts/popup/fragment/ConnectionEstablishingDialog.fragment.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /scripts/popup/fragment/PopoverActionSettings.fragment.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /scripts/popup/fragment/RecordDialog.fragment.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /scripts/popup/fragment/SelectAssociationGeneration.fragment.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 15 |