1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:
38:
39: class VideoTestWebTestCaseDriver {
40: protected $driver;
41: protected $testCase;
42: protected $functions;
43:
44: 45: 46: 47: 48: 49:
50: static public function attach($driver, $testCase){
51: if (is_a($driver, __CLASS__)) return $driver;
52: $class = __CLASS__;
53: $copy = new $class;
54: $copy->driver = $driver;
55: $copy->testCase = $testCase;
56: $copy->functions = new VideoTestWebTestCaseDriverFunctions();
57: $copy->functions->testCase = $testCase;
58: return $copy;
59: }
60:
61: 62: 63:
64: private $_lastCallWasVideoCall;
65:
66: 67: 68: 69: 70:
71: public function __get($name){
72: return $this->driver->$name;
73: }
74: 75: 76: 77: 78: 79:
80: public function __set($name, $value){
81: $this->driver->$name = $value;
82: }
83: 84: 85: 86: 87: 88: 89: 90:
91: public function __call($fn, $args){
92: if (preg_match('/^video/', $fn)){
93:
94: $result = call_user_func_array(array($this->functions, $fn), $args);
95: $this->_lastCallWasVideoCall = true;
96: } elseif (in_array($fn, array('getVerificationErrors', 'clearVerificationErrors'))){
97: if (!$this->_lastCallWasVideoCall){
98: return $this->driver->$fn();
99: } else {
100: return array();
101: }
102: } else {
103: $this->_lastCallWasVideoCall = false;
104: return call_user_func_array(array($this->driver, $fn), $args);
105: }
106: }
107:
108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131:
132: public function doTry($params = array()){
133: static $iteration = 0;
134: static $preventDistructors = array();
135: while (1){
136: $d = debug_backtrace();
137: $testFile = file_get_contents($d[4]['file']);
138: preg_match_all($pattern = '/class\s+([^ ]+)\s+extends\s+/ui', $testFile, $m);
139: if (!count($m[0])) throw new Exception($pattern.' not found');
140: $testClassName = array();
141: foreach ($m[0] as $k=>$v){
142: $className = $m[1][$k];
143: $newClassName = $className.$iteration;
144: $testFile = str_replace($v, 'class '.$newClassName.' extends ', $testFile);
145: $classNames[$className] = $newClassName;
146: if (is_a($className, 'CWebTestCase')) $testClassName = $newClassName;
147: }
148: $iteration ++;
149: $testFile = preg_replace('/^\<\?(php)?/ui', '', $testFile);
150: $paramNames = array();
151: foreach ($params as $k=>$v){
152: $paramNames[]='$'.$k;
153: }
154: $testFile = preg_replace('/(public\s+)?function\s+t\s*\(\)[ \t\r\n]*\{/', 'public function t('.implode(', ', $paramNames).'){', $testFile);
155: eval($testFile);
156: $test = new $newClassName;
157: $preventDistructors[] = $test;
158: $reflection = new ReflectionClass($test);
159: $property = $reflection->getProperty('drivers');
160: $property->setAccessible(true);
161:
162: $oldReflection = new ReflectionClass($this->testCase);
163: $oldProperty = $oldReflection->getProperty('drivers');
164: $oldProperty->setAccessible(true);
165: $property->setValue($test, $oldProperty->getValue($this->testCase));
166:
167:
168: try {
169: call_user_func_array(array($test, 't'), $params);
170: } catch (Exception $e){
171: print_r($e);
172: }
173: echo PHP_EOL.'press enter...'.PHP_EOL;
174: @flush(); @ob_flush();
175: fgets(STDIN);
176:
177: }
178:
179: }
180: 181: 182: 183: 184:
185: public function say($msg){
186: print_r($msg);
187: echo PHP_EOL;
188: if (strlen(print_r($msg, true)) < 100) var_dump($msg);
189: flush(); @ob_flush();
190: }
191: }
192:
193:
194: 195: 196:
197: class VideoTestWebTestCaseDriverFunctions {
198: public $overrideFastMode = false;
199: public $testCase;
200: const VIDEO_DEFAULT_MESSAGE_POSITION = 'top: 100px; left: 100px; right: 100px; height: 200px;';
201: public $videoDefaultMessagePosition = self::VIDEO_DEFAULT_MESSAGE_POSITION;
202:
203: 204: 205: 206: 207: 208:
209: public function videoSetVisible($element){
210: $x = $this->testCase->getElementPositionLeft($element);
211: $y = $this->testCase->getElementPositionTop($element);
212:
213: $step = $this->isFastModeOn()?50:15;
214: $windowHeight = $this->testCase->getEval('window.innerHeight');
215: $height = min($windowHeight * 80 / 100, $this->testCase->getElementHeight($element));
216:
217: while ((($y - $height/2) < (($currentYScroll = $this->testCase->getEval('window.document.documentElement.scrollTop')) + 50)) && $currentYScroll){
218: if ($this->isFastModeOn()){
219: $step = 50;
220: } else {
221: $diff = ($currentYScroll + 50) - ($y - $height/2);
222: if ($diff > 150) {
223: $step = min(500, intval($diff/5));
224: } else {
225: $step = 15;
226: }
227: }
228: $this->testCase->runScript("window.scrollBy(0,-".$step.");");
229: if (!$this->isFastModeOn()) usleep(10000);
230: }
231: $lastYScroll = -1;
232:
233: while ((($y + $height/2) > (($currentYScroll = $this->testCase->getEval('window.document.documentElement.scrollTop')) + $windowHeight - 50)) && ($currentYScroll != $lastYScroll)){
234: if ($this->isFastModeOn()){
235: $step = 50;
236: } else {
237: $diff = ($y + $height/2) - ($currentYScroll + $windowHeight - 50);
238: if ($diff > 150) {
239: $step = min(500, intval($diff/5));
240: } else {
241: $step = 15;
242: }
243: }
244: $lastYScroll = $currentYScroll;
245: $this->testCase->runScript("window.scrollBy(0,".$step.");");
246: if (!$this->isFastModeOn()) usleep(10000);
247: }
248: return $this->testCase;
249: }
250:
251: 252: 253: 254: 255: 256: 257: 258: 259: 260:
261:
262: public function videoMouseClick($element, $nearTheLeftSide=false, $highlightCallback=false){
263: $this->testCase->videoSetVisible($element);
264: if ($this->isSkipModeOn()) return;
265: $x = $this->testCase->getElementPositionLeft($element) + ($nearTheLeftSide?rand(5,20):($this->testCase->getElementWidth($element)*rand(20,80)/100));
266: $y = ($a=$this->testCase->getElementPositionTop($element)) + ($b=$this->testCase->getElementHeight($element))*($c=rand(20,80)/100) - ($d=$this->testCase->getEval('window.document.documentElement.scrollTop'));
267: if (is_array($nearTheLeftSide)){
268: if (substr($nearTheLeftSide[1], 0, 1) == '+'){
269: $x += substr($nearTheLeftSide[1], 1);
270: } elseif (substr($nearTheLeftSide[1], 0, 1) == '-'){
271: $x -= substr($nearTheLeftSide[1], 1);
272: } else {
273: $x = $nearTheLeftSide[1];
274: }
275: if (substr($nearTheLeftSide[0], 0, 1) == '+'){
276: $y += substr($nearTheLeftSide[0], 1);
277: } elseif (substr($nearTheLeftSide[0], 0, 1) == '-'){
278: $y -= substr($nearTheLeftSide[0], 1);
279: } else {
280: $y = $nearTheLeftSide[0] - $this->testCase->getEval('window.document.documentElement.scrollTop');
281: }
282: }
283:
284: $mouseStyle = ' background: url(\'data:image/gif;base64,R0lGODlhGQAdALMAAAAAAP///wD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAIALAAAAAAZAB0AAARGUMhJq7046827l8CXAaFoAUFpTmi6skGsmq381jYd766I7zPOD/gZEjtDkhIJzBVlraCwF/WpqsUK9ubk9rxS62tMLpsnEQA7\') no-repeat; position: fixed; z-index: 9999999; width: 25px; height: 29px;';
285: $script = array();
286:
287: if ($this->testCase->getEval('window.document.getElementById("mouse-cursor")?1:0')){
288: $screenCenterY = $this->testCase->getElementPositionTop('id=mouse-cursor')+4;
289: $screenCenterX = $this->testCase->getElementPositionLeft('id=mouse-cursor')+7;
290: $alreadyExists = true;
291: } else {
292: $windowHeight = $this->testCase->getEval('window.innerHeight');
293: $screenCenterY = $windowHeight/2;
294: $screenCenterX = 500;
295: $alreadyExists = false;
296: }
297: $hops = 20;
298: $opacity = $hops/4;
299: $coords = array();
300: for ($i = $this->isFastModeOn()?($hops-4):1; $i <= $hops; $i ++){
301: $coords[]=array(
302: intval($y-($screenCenterY-$y)/$hops+($screenCenterY-$y)/$i-4),
303: intval($x-($screenCenterX-$x)/$hops+($screenCenterX-$x)/$i-7),
304: (($i<$opacity)?(intval($i*10/$opacity)/10):1),
305: (!$this->isFastModeOn() && ($i < ($hops*3/4)))?40:0,
306: );
307: }
308: if ($alreadyExists){
309: $this->testCase->runScript('window.document.getElementById("mouse-cursor").setAttribute("data-done", 0);');
310: } else {
311: $this->testCase->runScript('var a = window.document.createElement("div"); a.setAttribute("id", "mouse-cursor"); a.setAttribute("style", "top:'.$screenCenterY.'px; left:'.$screenCenterX.'px; opacity: 0; '.$mouseStyle.'"); window.document.getElementsByTagName("body")[0].appendChild(a); ');
312: }
313: $this->testCase->runScript('(function(coords){ if (0 == coords.length) { window.document.getElementById("mouse-cursor").setAttribute("data-done", 1); } else { var nextItem = coords.shift(); var mouse = window.document.getElementById("mouse-cursor"); mouse.style.left = nextItem[1] + "px"; mouse.style.top = nextItem[0] + "px"; mouse.style.opacity = nextItem[2]; setTimeout(arguments.callee, nextItem[3], coords); }})('.CJSON::encode($coords).');');
314:
315: $t = time();
316: while (time() < ($t + 10)){
317: $done = $this->testCase->getEval('window.document.getElementById("mouse-cursor").getAttribute("data-done");');
318: if (($done === "1") || ($done === 1)) break;
319: usleep(100000);
320: }
321: $this->testCase->mouseOver($element);
322: if ($highlightCallback) call_user_func_array($highlightCallback, array($element));
323: if (!$this->isFastModeOn()) usleep(200000);
324: return $this->testCase;
325: }
326: 327: 328: 329: 330: 331: 332:
333: public function videoSleep($millis = 2000){
334: if (!$this->isFastModeOn()){
335: usleep($millis * 1000);
336: }
337: return $this->testCase;
338: }
339:
340: 341: 342: 343: 344: 345: 346:
347: public function videoCustomSelectBoxSelect($id, $value){
348: $this->videoMouseClick('xpath=//select[@id="'.$id.'"]/following-sibling::a[1]');
349: for ($exceptionTries = 0; $exceptionTries < 10; $exceptionTries++){
350: try{
351: $this->testCase->runScript($toggle='var e = document.createEvent("HTMLEvents"); e.initEvent("mousedown", false, true); e.which = 1; window.document.getElementById("'.$id.'").nextSibling.dispatchEvent(e); ');
352:
353: for ($tries = 200; $tries; $tries--){
354: if ($this->testCase->isElementPresent($q='xpath=//ul[contains(@class, "selectBox-options") and not(contains(@style, "display: none"))]/li/a[@rel="'.$value.'"]')){
355: $this->videoMouseClick($q);
356: break;
357: }
358: usleep(50000);
359: }
360: $name = $this->testCase->getText($q);
361: $this->testCase->select('id='.$id, 'value='.$value);
362: $this->testCase->runScript('window.document.getElementById("'.$id.'").nextSibling.children[0].innerHTML='.CJSON::encode($name).';');
363: $this->testCase->runScript($toggle);
364: return;
365: } catch (Exception $e){
366: sleep(1);
367: }
368: }
369: return $this->testCase;
370: }
371:
372: 373: 374: 375:
376: public function videoHideDatePicker(){
377: $this->testCase->runScript('(function(){var a = window.document.getElementsByClassName("ui-datepicker"); var i; for (i = 0; i < a.length; i++){a[i].style.display="none";}})();');
378: return $this->testCase;
379: }
380:
381: 382: 383: 384: 385: 386: 387:
388: public function videoSetDefaultMessagePosition($position = self::VIDEO_DEFAULT_MESSAGE_POSITION){
389: $this->videoDefaultMessagePosition = $position;
390: return $this->testCase;
391: }
392:
393: 394: 395: 396: 397: 398: 399: 400:
401: public function videoShowImage($files, $durations=5){
402: if ($this->isSkipModeOn()) return;
403: static $fnNameCounter = 1;
404: if (!is_array($files)) $files=array($files);
405: if (!is_array($durations)) $durations=array($durations);
406:
407: $zindex = 10000000;
408: $style = 'position: fixed; top: 100px; left: 100px; width:100%; height: 100%; display: block; z-index: '.$zindex.';';
409:
410: $imgStyle = 'position: fixed; top: 100px; left: 100px;';
411: $this->testCase->runScript('var a = window.document.createElement("div"); a.setAttribute("id", "video-image"); a.setAttribute("style", "'.$style.'"); a.innerHTML='.CJSON::encode('<img id="video-image-img-1" src="" style="'.$imgStyle.'; opacity: 0; z-index: '.$zindex.';"/><img id="video-image-img-2" src="" style="'.$imgStyle.'; opacity: 0; z-index: '.$zindex.';"/>').'; window.document.getElementsByTagName("body")[0].appendChild(a);');
412:
413: $step = 1;
414: foreach ($files as $file){
415: $zindex ++;
416: $this->testCase->runScript('var a = window.document.getElementById("video-image-img-'.$step.'"); a.setAttribute("src", '.($file?CJSON::encode('data:image/png;base64,'.base64_encode(file_get_contents($file))):'""').'); var videoImageCounter'.$fnNameCounter.' = 0; function videoImageUpdate'.$fnNameCounter.'(){ a.setAttribute("style", "'.$imgStyle.' opacity: "+(videoImageCounter'.$fnNameCounter.'/100)+"; z-index: '.$zindex.';"); videoImageCounter'.$fnNameCounter.'+='.($this->isFastModeOn()?50:2).'; if (videoImageCounter'.$fnNameCounter.' <= 100) { window.setTimeout(videoImageUpdate'.$fnNameCounter.', 5); } } videoImageUpdate'.$fnNameCounter.'();');
417:
418: $step = 3 - $step;
419: if (count($durations)){
420: $delay = array_shift($durations);
421: } else {
422: if (!$delay) $delay = 5;
423: }
424:
425: if (!$this->isFastModeOn()) sleep($delay);
426: }
427: $this->testCase->runScript('window.document.getElementById("video-image-img-'.$step.'").setAttribute("style", "opacity: 0;"); var a = window.document.getElementById("video-image-img-'.(3-$step).'"); a.setAttribute("src", '.($file?CJSON::encode('data:image/png;base64,'.base64_encode(file_get_contents($file))):'""').'); var videoImageCounter'.$fnNameCounter.' = 0; function videoImageUpdate'.$fnNameCounter.'(){ a.setAttribute("style", "'.$imgStyle.' opacity: "+((100-videoImageCounter'.$fnNameCounter.')/100)+"; z-index: '.$zindex.';"); videoImageCounter'.$fnNameCounter.'+='.($this->isFastModeOn()?50:2).'; if (videoImageCounter'.$fnNameCounter.' <= 100) { window.setTimeout(videoImageUpdate'.$fnNameCounter.', 5); } } videoImageUpdate'.$fnNameCounter.'();');
428:
429: if (!$this->isFastModeOn()) sleep($delay);
430:
431: $this->testCase->runScript('var a = window.document.getElementById("video-image"); a.parentNode.removeChild(a);');
432: return $this->testCase;
433: }
434: 435: 436: 437: 438: 439:
440: public function videoShowMessage($text, $position=null, $moreMsToWait = 0){
441: if ($this->isSkipModeOn()) return;
442: if (!$position) $position = $this->videoDefaultMessagePosition;
443: $pc = array('');
444: $text .= ' ';
445: $i=0;
446: while (preg_match('/^(.)(.+)$/su', $text, $m)){
447: if (in_array($m[1], array("\n", ' '))){
448: $pc[]=array(false, $m[1]);
449: $pc[]=array();
450: } else {
451: $pc[max(array_keys($pc))][]=$m[1];
452: }
453: $text = $m[2];
454: }
455: $videoMessageStyle = 'position: fixed; '.$position.' display: block; background-color: #fff; padding: 10px; font-size: 20px; z-index: 10000000; border: #bbb solid 10px; border-radius: 20px; overflow: hidden;';
456:
457: $blinkingCursor = '<div id="bfwebtestcasevideodriverblinkerdiv">_</div>';
458: $this->testCase->runScript('var a = window.document.createElement("div"); a.setAttribute("id", "video-message"); a.setAttribute("style", "'.$videoMessageStyle.'"); a.innerHTML='.CJSON::encode('<style>@keyframes bfwebtestcasevideodriverblinker { 0% { opacity: 1.0; } 50% { opacity: 0.0; } 100% { opacity: 1.0; }} #bfwebtestcasevideodriverblinkerdiv {display: inline-block; width: 10px; margin-right: -10px; animation-name: bfwebtestcasevideodriverblinker; animation-duration: 1s; animation-timing-function: step-end; font-weight: bold; animation-iteration-count: infinite;}</style><table style="position: absolute; border: none; bottom: 2px;"><tr style="border: none;"><td id="video-message-text" style="padding: 2px 20px 2px 2px; font-size: 22px; color: #6a6a6a; font-family: Verdana, Tahoma, Arial, Helvetica, sans-serif; border: none; text-align: left;">'.$blinkingCursor.'</td></tr></table>').'; window.document.getElementsByTagName("body")[0].appendChild(a);');
459:
460:
461: $contents = '';
462: $codeSequence = array();
463: foreach ($pc as $piece){
464: if (!is_array($piece) || !count($piece) || !isset($piece[0])) continue;
465: if ($piece[0] === false){
466: $contents .= ($piece[1] == "\n")?'<br/>':htmlentities($piece[1]);
467: } else {
468: for ($k = 0; $k <= count($piece); $k++) {
469: $visible = '';
470: $invisible = '';
471: for ($i=0; $i<$k; $i++){
472: $visible.=$piece[$i];
473: }
474: for ($i=$k; $i<count($piece); $i++){
475: $invisible.=$piece[$i];
476: }
477: $lastWord = '<nobr style="font-size: inherit;"><span style="font-size: inherit;">'.htmlentities($visible).'</span>'.$blinkingCursor.'<span style="color: #fff; font-size: inherit;">'.htmlentities($invisible).'</span></nobr>';
478: $lastWordNoBlink = '<nobr style="font-size: inherit;"><span style="font-size: inherit;">'.htmlentities($visible).'</span><span style="color: #fff; font-size: inherit;">'.htmlentities($invisible).'</span></nobr>';
479:
480: if ($this->isFastModeOn()) {
481: } else {
482: $codeSequence[] = array($contents.$lastWord, 50);
483: }
484: }
485: $contents .= $lastWordNoBlink;
486: }
487: }
488:
489: if ($this->isFastModeOn()) {
490: $codeSequence[] = array($contents, 500+$moreMsToWait);
491: } else {
492: $lastItem = array_pop($codeSequence);
493: $lastItem[1] += 2000+$moreMsToWait;
494: array_push($codeSequence, $lastItem);
495: }
496: $this->testCase->runScript('(function(){var bftvvideomessage='.CJSON::encode($codeSequence).'; var bftvvideomessagefunction=function(){ if (0 == bftvvideomessage.length) { var a = window.document.getElementById("video-message"); a.parentNode.removeChild(a); } else { var nextItem = bftvvideomessage.shift(); window.document.getElementById("video-message-text").innerHTML = nextItem[0]; setTimeout(bftvvideomessagefunction, nextItem[1]); }}; bftvvideomessagefunction();})();');
497: while ($this->testCase->isElementPresent('id=video-message')) usleep(100000);
498: return $this->testCase;
499: }
500: 501: 502: 503: 504: 505:
506: public function videoType($element, $text){
507: if ($this->isFastModeOn()){
508: $this->testCase->type($element, $text);
509: return;
510: }
511: $existingId = false;
512: try {
513: $existingId = $this->testCase->getAttribute($element.'@id');
514: } catch (Exception $e){}
515: if ($existingId){
516: $idToUse = $existingId;
517: } else {
518: $idToUse = 'videoTestDriverVideoTypeInput'.rand(100000,900000);
519: $this->testCase->assignId($element, $idToUse);
520: }
521: $markElement = 'videotestdrivervideotypemark'.rand(100000, 900000);
522: $this->testCase->runScript('(function(){ window.document.getElementsByTagName("body")[0].appendChild(window.document.createElement('.CJSON::encode($markElement).')); var text = '.CJSON::encode((string)$text.'').'; var putChar = function(i){ if (i > text.length) { var marks = window.document.getElementsByTagName('.CJSON::encode($markElement).'); var j; for (j = 0; j < marks.length; j++) { marks[j].parentNode.removeChild(marks[j]); } return; } window.document.getElementById('.CJSON::encode($idToUse).').value = text.substr(0, i); setTimeout(function(){putChar(i+1);},100);}; putChar(1);})();');
523: for ($t = time(); ($this->testCase->getEval('window.document.getElementsByTagName('.CJSON::encode($markElement).').length')) && (time() < $t + 60); ){
524: usleep(300000);
525: }
526: if ($this->testCase->getEval('window.document.getElementsByTagName('.CJSON::encode($markElement).').length')){
527: $this->testCase->fail(__CLASS__.' could not remove mark '.$markElement);
528: }
529: if ($idToUse !== $existingId){
530: $this->testCase->assignId('id='.$idToUse, $existingId);
531: }
532: return $this->testCase;
533: }
534: 535: 536: 537: 538:
539: public function videoStart($name){
540: if (!$this->isFastModeOn()){
541: exec('selenium-video '.$name.' 2>&1');
542: }
543: return $this->testCase;
544: }
545: 546: 547: 548:
549: public function videoStop(){
550: if (!$this->isFastModeOn()){
551: sleep(5);
552: exec('selenium-video stop 2>&1');
553: }
554: return $this->testCase;
555: }
556:
557: 558: 559: 560: 561: 562:
563: public function videoInit(){
564: $this->testCase->open('');
565: $this->testCase->deleteAllVisibleCookies();
566: $this->testCase->createCookie('ENVIRONMENT=SELENIUM_TEST', 'path=/');
567: $this->testCase->open('');
568: $this->testCase->windowMaximize();
569: return $this->testCase;
570: }
571:
572: 573: 574: 575:
576: public function videoFast() {
577: $this->overrideFastMode = 1;
578: return $this->testCase;
579: }
580:
581: 582: 583: 584:
585: public function videoSlow() {
586: $this->overrideFastMode = 0;
587: return $this->testCase;
588: }
589:
590: 591: 592: 593: 594:
595: public function videoSkip(){
596: $this->overrideFastMode = 2;
597: return $this->testCase;
598: }
599:
600: 601: 602: 603:
604: public function videoDefault() {
605: $this->overrideFastMode = false;
606: return $this->testCase;
607: }
608:
609: 610: 611:
612: public function isFastModeOn(){
613: if (!isset(Yii::app()->params['selenium-video']['ignore-fast-override']) || !Yii::app()->params['selenium-video']['ignore-fast-override']){
614: if ($this->overrideFastMode !== false) {
615: return $this->overrideFastMode;
616: }
617: }
618: return (isset(Yii::app()->params['selenium-video']['fast']) && Yii::app()->params['selenium-video']['fast'])?Yii::app()->params['selenium-video']['fast']:false;
619: }
620: 621: 622:
623: public function isSkipModeOn(){
624: return ($this->isFastModeOn() === 2);
625: }
626: }
627:
628: