An online acquaintance described it like this:
„You build your tool to reduce grinding. After a while, building the tool becomes a game in itself. One where you make the rules“
Multiplayer online games nowadays heavily rely on small repetitive tasks to regulate the pace in which you proceed in your competition with other players. Duel a player once and win to earn a point. You have enough energy to duel 10 times. Then you have to wait to regain your energy. If the designer did a good job, it’s fun at first. After a while it gets boring, but you have to do it to compete.
My first encounter with a tool for this was Autohotkey. The game was a flash game. The drawbacks were:
- I needed to learn a completely new programming language
- The computer had to run Windows
- The computer was completely blocked while the script ran
- Detecting information in a flashgame is tedious, in short you have to compare pixel colors
- Finding places to click or type text into was reliant on measuring relative coordinates
Later, i heard from a coworker that she used Selenium IDE to control an HTML browser game. This sounded like a way better solution, so i immediately started on a Selenese script for a game i was playing at the time. After installing the Selenium IDE in my Firefox, the first task was to duel a player 10 times, since that was the daily limit. A little obstacle was the way the game was rendered – boxes inside boxes like russian dolls, very little classes, dynamically generated id attributes. Thats where Selenese’s locators really rock. You have a wide variety to choose from. Xpath did the job for me (not the most performant solution, but that is not an issue in an interactive session):
click //div[contains(@id,'button_battle_')] waitForVisible //div[contains(@id,'region_map')]/div/div[3]//a assertElementPresent //div[contains(@id, 'reward')]/div[contains(text(),'Winner')] click //div[contains(text(),'Rematch')] waitForVisible //div[contains(text(),'Rematch')] assertElementPresent //div[contains(@id, 'reward')]/div[contains(text(),'Winner')] click //div[contains(text(),'Rematch')] waitForVisible //div[contains(text(),'Rematch')] ... another 7 times
This is was the first working prototype – click on a button with an id of „button_battle_<playerid>“, utilizing waitForVisible to let the AJAX request complete, assert I won the duel, click a button labelled „Rematch“ on the result page, wait again, check for victory. If I lost, the script would just stop, else 10 rounds would be fought.
Since this was nice, but a bit verbose, I looked for some control structures in Selenese and found out that another Firefox Extension was needed, SelBlocks, which provides If/else, for/foreach and goto, among other constructs. With this, the above can be improved:
click //div[contains(@id,'button_battle_')] waitForVisible //div[contains(@id,'region_map')]/div/div[3]//a store 0 Victories label Repeating storeElementPresent //div[contains(@id, 'reward')]/div[contains(text(),'Winner')] Win storeEval (function(vic,chg){ if (chg) return vic + 1; else return vic - 1 })(${Vic},${Win}) Victories gotoIf ${Victories} < -1 Endpoint click //div[contains(text(),'Rematch')] waitForVisible //div[contains(text(),'Rematch')] | //div[@id='navspace']/div/a storeElementPresent //a[contains(.,'Battle list')] Ended gotoIf Ended Endpoint goto Repeating label Endpoint
So here I track how many times i won and lost, and only if the odds are against me, I bail. I need to check whether the game tells me I reached the daily for that particular opponent. An improvement, but it still requires my attention choosing my next victim.
The next step was to extend Selenium IDE with a custom Javascript function that could choose the best duel odds from a list of players. After choosing one, the script would then proceed to battle a player. To have some kind of idea how it went, i started to print out the names of my opponents along with the tally:
storeText //a[contains(@class,'dark_link')] heroname getEval LOG.warn("Hero ${heroname} result: ${Victories}")
The next thing was to monitor my stamina, the form of energy used for the duels:
label Duel storeText //span[@id="player_info_stamina"] Stamina gotoIf ${Stamina} < 10 Waitlabel ... duel on label Waitlabel pause 1800000 goto Duel
This was the point where the browser at home could play the game for me for hours while I was working in the office. As an added benefit, at home I could browse in a different Firefox tab without disturbing Selenium at all! It made playing much more enjoyable, and my guild loved me for my constant weekly performance.
But when something is working, an engineer thinks about what to optimize. Think multiple heroes. Playing one account requires one Selenium IDE. Sadly the IDE only is available for Firefox, and at the time I did not know about how to run multiple Firefox profiles at once.
The obvious choice, Selenium RC or Selenium 2, was a kind of a dead end – Installation is wonky and PHP support is rudimentary and not well documented. Since I could not find anyone who could help me with using Selenium 2, i followed a hint from a coworker and tried RobotFramework. After my problematic experiences with Selenium 2, I was really impressed by the ease of the Installation. Just by entering
sudo apt-get install python python-pip python-wxgtk2.8 firefox sudo pip install selenium robotframework robotframework-ride robotframework-selenium2library
Everything is working from inside a fully-fledged IDE, which is more powerful and almost as easy to use as Selenium IDE. RobotFramework puts an abstraction onto Selenium 2 that is intuitive to Selenese users but far more powerful.
*** Settings *** Library Selenium2Library *** Variables *** ${GAME_URL} ************************ ${BROWSER} firefox ${LOGIN_URL} http://${GAME_URL}/login.php ${MY_URL} http://${GAME_URL}/?ME=${MY_ID} ${MY_ID} ********************** *** Keywords *** Open Login Page Open Browser ${LOGIN_URL} ${BROWSER} ff_profile_dir=/home/user/.mozilla/firefox/****** Switch Browser 1 Element Should be Visible pwd set selenium speed 0.1 seconds DoLogin [Arguments] ${USER}=****** ${PASS}=****** Input Text hero ${USER} Input Password pwd ${PASS} Click Element button-login Wait until page contains Element id=news_popup Click Element css=#button-news-popup > span
This first defines a set of variables to centralize the configuration. Afterwards, two subroutines (which RobotFramework calls keywords) are defined. One opens a browser and selects it and sets a default delay for test runs. The second one fills out a login form and clicks the submit button.
A first „test suite“ for this game then looked like this:
*** Settings *** Resource Ressources.txt Resource Combat.txt *** Test Cases *** CombatTestCase Open Login Page DoLogin Hit Enemies
All can be run directly from the IDE which starts up the specified Firefox profile (the default profile that comes with RobotFramework is working, but if you want to keep cookies or have additional extensions installed, you need to prepare it). The code is easily modularized and reusable and offers lots of potential for further improvement. The only drawback so far is the slightly weaker support for AJAX, but that also applies to Selenium 2; since neither runs directly in the browser, every attempt at waitForVisible is of course less reliable than a piece of Javascript that can access the DOM directly, like Selenium IDE does it.
Why use this and not Greasemonkey? Because i can. And it’s fun. And an easy way to get familiar with Selenium.
Schreibe einen Kommentar