MouseApiTest.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. <?php
  2. /*
  3. * This file is part of Chrome PHP.
  4. *
  5. * (c) Soufiane Ghzal <sghzal@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace HeadlessChromium\Test;
  11. use Generator;
  12. use HeadlessChromium\Browser;
  13. use HeadlessChromium\BrowserFactory;
  14. use HeadlessChromium\Dom\Selector\CssSelector;
  15. use HeadlessChromium\Dom\Selector\Selector;
  16. use HeadlessChromium\Dom\Selector\XPathSelector;
  17. /**
  18. * @covers \HeadlessChromium\Browser
  19. * @covers \HeadlessChromium\Page
  20. */
  21. class MouseApiTest extends BaseTestCase
  22. {
  23. /**
  24. * @var Browser\ProcessAwareBrowser
  25. */
  26. public static $browser;
  27. public static function setUpBeforeClass(): void
  28. {
  29. parent::setUpBeforeClass();
  30. $factory = new BrowserFactory();
  31. self::$browser = $factory->createBrowser();
  32. }
  33. public static function tearDownAfterClass(): void
  34. {
  35. parent::tearDownAfterClass();
  36. self::$browser->close();
  37. }
  38. private function openSitePage($file)
  39. {
  40. $page = self::$browser->createPage();
  41. $page->navigate(self::sitePath($file))->waitForNavigation();
  42. return $page;
  43. }
  44. /**
  45. * @throws \HeadlessChromium\Exception\CommunicationException
  46. * @throws \HeadlessChromium\Exception\NoResponseAvailable
  47. */
  48. public function testClickLink(): void
  49. {
  50. // initial navigation
  51. $page = $this->openSitePage('b.html');
  52. $rect = $page
  53. ->evaluate('JSON.parse(JSON.stringify(document.querySelector("#a").getBoundingClientRect()));')
  54. ->getReturnValue();
  55. $page->mouse()->move(\ceil($rect['x']), \ceil($rect['y']))->click();
  56. $page->waitForReload();
  57. $title = $page->evaluate('document.title')->getReturnValue();
  58. self::assertEquals('a - test', $title);
  59. }
  60. /**
  61. * @throws \HeadlessChromium\Exception\CommunicationException
  62. * @throws \HeadlessChromium\Exception\NoResponseAvailable
  63. */
  64. public function testScroll(): void
  65. {
  66. // initial navigation
  67. $page = $this->openSitePage('bigLayout.html');
  68. // scroll 100px down
  69. $page->mouse()->scrollDown(100);
  70. $windowScrollY = $page->evaluate('window.scrollY')->getReturnValue();
  71. self::assertEquals(100, $windowScrollY);
  72. self::assertEquals(100, $page->mouse()->getPosition()['y']);
  73. // scrolling 100px up should revert the last action
  74. $page->mouse()->scrollUp(100);
  75. $windowScrollY = $page->evaluate('window.scrollY')->getReturnValue();
  76. self::assertEquals(0, $windowScrollY);
  77. self::assertEquals(0, $page->mouse()->getPosition()['y']);
  78. // try to scroll more than possible
  79. $page->mouse()->scrollDown(10000);
  80. $windowScrollY = $page->evaluate('window.scrollY')->getReturnValue();
  81. self::assertLessThan(10000, $windowScrollY);
  82. self::assertLessThan(10000, $page->mouse()->getPosition()['y']);
  83. }
  84. /**
  85. * @dataProvider providerFindElement_withSingleElement
  86. *
  87. * @throws \HeadlessChromium\Exception\CommunicationException
  88. * @throws \HeadlessChromium\Exception\NoResponseAvailable
  89. */
  90. public function testFindElement_withSingleElement(Selector $selector): void
  91. {
  92. // initial navigation
  93. $page = $this->openSitePage('b.html');
  94. $page->mouse()->findElement($selector)->click();
  95. $page->waitForReload();
  96. $title = $page->evaluate('document.title')->getReturnValue();
  97. self::assertEquals('a - test', $title);
  98. }
  99. /**
  100. * @return Generator<string, array{Selector}>
  101. */
  102. public function providerFindElement_withSingleElement(): Generator
  103. {
  104. yield 'css' => [new CssSelector('#a')];
  105. yield 'xpath' => [new XPathSelector('//*[@id="a"]')];
  106. }
  107. /**
  108. * @dataProvider providerFindElement_afterMove
  109. *
  110. * @throws \HeadlessChromium\Exception\CommunicationException
  111. * @throws \HeadlessChromium\Exception\NoResponseAvailable
  112. */
  113. public function testFindElement_afterMove(Selector $selector): void
  114. {
  115. // initial navigation
  116. $page = $this->openSitePage('b.html');
  117. $page->mouse()->move(1000, 1000);
  118. $page->mouse()->findElement($selector)->click();
  119. $page->waitForReload();
  120. $title = $page->evaluate('document.title')->getReturnValue();
  121. self::assertEquals('a - test', $title);
  122. }
  123. /**
  124. * @return Generator<string, array{Selector}>
  125. */
  126. public function providerFindElement_afterMove(): Generator
  127. {
  128. yield 'css' => [new CssSelector('#a')];
  129. yield 'xpath' => [new XPathSelector('//*[@id="a"]')];
  130. }
  131. /**
  132. * @dataProvider providerFindElement_withMultipleElements
  133. *
  134. * @throws \HeadlessChromium\Exception\CommunicationException
  135. * @throws \HeadlessChromium\Exception\NoResponseAvailable
  136. */
  137. public function testFindElement_withMultipleElements(Selector $selector, int $position, string $expectedPageTitle): void
  138. {
  139. $page = $this->openSitePage('b.html');
  140. $page->mouse()->findElement($selector, $position)->click();
  141. $page->waitForReload();
  142. $title = $page->evaluate('document.title')->getReturnValue();
  143. self::assertEquals($expectedPageTitle, $title);
  144. }
  145. /**
  146. * @return Generator<array-key, array{Selector, int, string}>
  147. */
  148. public function providerFindElement_withMultipleElements(): Generator
  149. {
  150. $cssSelector = new CssSelector('.a');
  151. $xPathSelector = new XPathSelector('//*[@class="a"]');
  152. foreach (['css' => $cssSelector, 'xpath' => $xPathSelector] as $type => $selector) {
  153. yield $type.' – 1' => [$selector, -1, 'c - test'];
  154. yield $type.' – 2' => [$selector, 2, 'b - test'];
  155. yield $type.' – 3' => [$selector, 99, 'a - test'];
  156. }
  157. }
  158. /**
  159. * @dataProvider providerFindElement_withScrolling
  160. *
  161. * @throws \HeadlessChromium\Exception\CommunicationException
  162. * @throws \HeadlessChromium\Exception\NoResponseAvailable
  163. */
  164. public function testFindElement_withScrolling(Selector $selector): void
  165. {
  166. // initial navigation
  167. $page = $this->openSitePage('bigLayout.html');
  168. $page->mouse()->findElement($selector);
  169. $page->mouse()->click();
  170. $page->waitForReload();
  171. $title = $page->evaluate('document.title')->getReturnValue();
  172. self::assertEquals('a - test', $title);
  173. }
  174. /**
  175. * @return Generator<string, array{Selector}>
  176. */
  177. public function providerFindElement_withScrolling(): Generator
  178. {
  179. yield 'css' => [new CssSelector('#bottomLink')];
  180. yield 'xpath' => [new XPathSelector('//*[@id="bottomLink"]')];
  181. }
  182. /**
  183. * @dataProvider providerFindElement_withMissingElement
  184. *
  185. * @throws \HeadlessChromium\Exception\CommunicationException
  186. * @throws \HeadlessChromium\Exception\NoResponseAvailable
  187. * @throws \HeadlessChromium\Exception\ElementNotFoundException
  188. */
  189. public function testFindElement_withMissingElement(Selector $selector): void
  190. {
  191. $this->expectException(\HeadlessChromium\Exception\ElementNotFoundException::class);
  192. // initial navigation
  193. $page = $this->openSitePage('b.html');
  194. $page->mouse()->findElement($selector);
  195. }
  196. /**
  197. * @return Generator<string, array{Selector}>
  198. */
  199. public function providerFindElement_withMissingElement(): Generator
  200. {
  201. yield 'css' => [new CssSelector('#missing')];
  202. yield 'xpath' => [new XPathSelector('//*[@id="missing"]')];
  203. }
  204. /**
  205. * @throws \HeadlessChromium\Exception\CommunicationException
  206. * @throws \HeadlessChromium\Exception\NoResponseAvailable
  207. */
  208. public function testGetPosition(): void
  209. {
  210. // initial navigation
  211. $page = $this->openSitePage('b.html');
  212. self::assertEquals(['x' => 0, 'y' => 0], $page->mouse()->getPosition());
  213. // find element with id "a"
  214. $page->mouse()->find('#a');
  215. $x = $page->mouse()->getPosition()['x'];
  216. $y = $page->mouse()->getPosition()['y'];
  217. self::assertGreaterThanOrEqual(1, $x); // 8
  218. self::assertLessThanOrEqual(51, $x);
  219. self::assertGreaterThanOrEqual(1, $y); // 87
  220. self::assertLessThanOrEqual(107, $y);
  221. }
  222. }