/* * Sakuli - Testing and Monitoring-Tool for Websites and common UIs. * * Copyright 2013 - 2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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. */ package org.sakuli.actions.screenbased; import org.sakuli.actions.Action; import org.sakuli.exceptions.SakuliCipherException; import org.sikuli.script.Button; import org.sikuli.script.FindFailed; import org.springframework.util.StringUtils; import java.util.concurrent.TimeUnit; /** * @author Tobias Schneck */ public class TypingUtil<A extends Action> { private A action; public TypingUtil(A action) { this.action = action; } /******************************************************** * PASTE FUNCTIONS *******************************************************/ /** * pastes the text at the current position of the focus/carret * <br> * using the clipboard and strg/ctrl/cmd-v (paste keyboard shortcut) * * @param text a string, which might contain unicode characters * @return this {@link A} or NULL on errors. */ public A paste(String text) { return pasteImpl(text, true); } /** * makes a masked {@link #paste(String)} without any logging. * * @param text a string, which might contain unicode characters * @return this {@link A} or NULL on errors. */ public A pasteMasked(String text) { return pasteImpl(text, false); } /** * combines {@link #pasteMasked(String)} and {@link #decryptSecret(String)}. * * @param text encrypted secret * @return this {@link A} or NULL on errors. */ public A pasteAndDecrypt(String text) { return pasteImpl(decryptSecret(text), false); } private A pasteImpl(String text, boolean logging) { int returnVal = action.getActionRegion().paste(text); if (returnVal != 1) { if (!logging) { text = "*****"; } action.getLoader().getExceptionHandler().handleException("during pasting of text \"" + text + "\" something went wrong.", action.getActionRegion(), action.getResumeOnException()); return null; } return action; } /******************************************************** * TYPE FUNCTIONS *******************************************************/ /** * Enters the given text one character/key after another using keyDown/keyUp. * <p> * About the usable Key constants see documentation of {@link org.sikuli.script.Key}. * The function could also type UTF-8 unicode characters, if the OS supports it. * The text is entered at the current position of the focus. * </p> * * @param text containing characters and/or {@link org.sikuli.script.Key} constants * @param optModifiers (optional) an String with only {@link org.sikuli.script.Key} constants. * @return this {@link A} or NULL on errors. */ public A type(String text, String optModifiers) { if (StringUtils.isEmpty(optModifiers)) { return typeImpl(text, true); } return typeModifiedImpl(text, optModifiers, true); } /** * Enters the given text one character/key after another using keyDown/keyUp. * The entered text will be masked at the logging. * <p> * About the usable Key constants see documentation of {@link org.sikuli.script.Key}. * The function could also type UTF-8 unicode characters, if the OS supports it. * The text is entered at the current position of the focus. * </p> * * @param text containing characters and/or {@link org.sikuli.script.Key} constants * @param optModifiers (optional) an String with only {@link org.sikuli.script.Key} constants. * @return this {@link A} or NULL on errors. */ public A typeMasked(String text, String optModifiers) { if (StringUtils.isEmpty(optModifiers)) { return typeImpl(text, false); } return typeModifiedImpl(text, optModifiers, false); } /** * Decrypt and enters the given text one character/key after another using keyDown/keyUp. * The entered text will be masked at the logging. For the deatails of the decryption see {@link #decryptSecret(String)}. * <p> * About the usable Key constants see documentation of {@link org.sikuli.script.Key}. * The function could also type UTF-8 unicode characters, if the OS supports it. * The text is entered at the current position of the focus. * </p> * * @param text containing characters and/or {@link org.sikuli.script.Key} constants * @param optModifiers (optional) an String with only {@link org.sikuli.script.Key} constants. * @return this {@link A} or NULL on errors. */ public A typeAndDecrypt(String text, String optModifiers) { if (StringUtils.isEmpty(optModifiers)) { return typeImpl(decryptSecret(text), false); } return typeModifiedImpl(decryptSecret(text), optModifiers, false); } private A typeImpl(String text, boolean logging) { int returnValue = action.getActionRegion().type(text); /** * this is needed because in the methode {@link org.sikuli.script.Region#keyin(Object, String, int)} * will be reset the type delay to 0.0. * */ action.getLoader().loadSettingDefaults(); if (returnValue == 1) { return action; } if (logging) { text = "****"; } action.getLoader().getExceptionHandler().handleException("could n't type in the text \"" + text + "\"", action.getActionRegion(), action.getResumeOnException()); return null; } private A typeModifiedImpl(String text, String modifiers, boolean logging) { int returnValue = action.getActionRegion().type(text, modifiers); /** * this is needed because in the methode {@link org.sikuli.script.Region#keyin(Object, String, int)} * will be reset the type delay to 0.0. * */ action.getLoader().loadSettingDefaults(); if (returnValue == 1) { return action; } if (logging) { text = "****"; } action.getLoader().getExceptionHandler().handleException("could n't type with modifiers the text \"" + text + "\"", action.getActionRegion(), action.getResumeOnException()); return null; } /** * press and hold the given keys including modifier keys <br>use the key constants defined in class Key, <br>which * only provides a subset of a US-QWERTY PC keyboard layout <br>might be mixed with simple characters * <br>use + to concatenate Key constants * * @param keys valid keys */ public A keyDown(String keys) { try { action.getActionRegion().keyDown(keys); } catch (Exception e) { action.getLoader().getExceptionHandler().handleException(e, action.getActionRegion(), action.getResumeOnException()); } return action; } /** * release the given keys (see {@link TypingUtil#keyDown(String)}). * * @param keys valid keys */ public A keyUp(String keys) { try { action.getActionRegion().keyUp(keys); } catch (Exception e) { action.getLoader().getExceptionHandler().handleException(e, action.getActionRegion(), action.getResumeOnException()); } return action; } /** * Compact alternative for type() with more options <br> * - special keys and options are coded as #XN. or #X+ or #X- <br> * where X is a refrence for a special key and N is an optional repeat factor <br> * A modifier key as #X. modifies the next following key<br> * the trailing . ends the special key, the + (press and hold) or - (release) does the same, <br> * but signals press-and-hold or release additionally.<br> * except #W / #w all special keys are not case-sensitive<br> * a #wn. inserts a wait of n millisecs or n secs if n less than 60 <br> * a #Wn. sets the type delay for the following keys (must be > 60 and denotes millisecs) * - otherwise taken as normal wait<br> * Example: wait 2 secs then type CMD/CTRL - N then wait 1 sec then type DOWN 3 times<br> * Windows/Linux: write("#w2.#C.n#W1.#d3.")<br> * Mac: write("#w2.#M.n#W1.#D3.")<br> * for more details about the special key codes and examples consult the docs <br> * * @param text a coded text interpreted as a series of key actions (press/hold/release) */ public A write(String text) { try { action.getActionRegion().write(text); // will needed because sikuli write method will modify the type delay settings. action.getLoader().loadSettingDefaults(); } catch (Exception e) { action.getLoader().getExceptionHandler().handleException(e, action.getActionRegion(), action.getResumeOnException()); } return action; } /* *Decrypt a encrypted secret and returns the value at runtime. * The decryption will only work if the encryption and decryption happen on the same physical machine. * There will be no logging with the decrypted secret during this step. * <p/> * To create a encrypted secret see "README.txt". * * @param secret encrypted secret as {@link String} * @return decrypted {@link String} */ public String decryptSecret(String secret) { try { return action.getLoader().getCipherUtil().decrypt(secret); } catch (SakuliCipherException e) { action.getLoader().getExceptionHandler().handleException(e, action.getResumeOnException()); } return null; } /** * move the mouse pointer to the given target location and move the * wheel the given steps down. * * @param steps the number of steps */ public A mouseWheelDown(int steps) { return mouseWheel(Button.WHEEL_DOWN, steps); } /** * move the mouse pointer to the given target location and move the * wheel the given steps up. * * @param steps the number of steps */ public A mouseWheelUp(int steps) { return mouseWheel(Button.WHEEL_UP, steps); } private A mouseWheel(int wheelDirection, int steps) { int ret; try { ret = action.getActionRegion().wheel(action, wheelDirection, steps); } catch (FindFailed findFailed) { ret = 0; } if (ret != 1) { action.getLoader().getExceptionHandler().handleException("could not interact with the mouse wheel in this region", action.getActionRegion(), action.getResumeOnException()); return null; } return action; } /** * Blocks the current testcase execution for x milliseconds * * @param milliseconds to sleep * @return this {@link A} or NULL on errors. */ public A sleep(Long milliseconds) { try { TimeUnit.MILLISECONDS.sleep(milliseconds); } catch (InterruptedException e) { action.getLoader().getExceptionHandler().handleException(e, true); return null; } return action; } }