PuphpeteerTest.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. <?php
  2. namespace Nesk\Puphpeteer\Tests;
  3. use Nesk\Puphpeteer\Puppeteer;
  4. use Nesk\Rialto\Data\JsFunction;
  5. use Symfony\Component\Process\Process;
  6. use PHPUnit\Framework\ExpectationFailedException;
  7. use Nesk\Puphpeteer\Resources\ElementHandle;
  8. class PuphpeteerTest extends TestCase
  9. {
  10. public function setUp(): void
  11. {
  12. parent::setUp();
  13. $this->host = '127.0.0.1:8089';
  14. $this->url = "http://{$this->host}";
  15. $this->serverDir = __DIR__.'/resources';
  16. $this->servingProcess = new Process("php -S {$this->host} -t {$this->serverDir}");
  17. $this->servingProcess->start();
  18. // Chrome doesn't support Linux sandbox on many CI environments
  19. // See: https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md#chrome-headless-fails-due-to-sandbox-issues
  20. $this->browserOptions = ['args' => ['--no-sandbox', '--disable-setuid-sandbox']];
  21. if ($this->canPopulateProperty('browser')) {
  22. $this->browser = (new Puppeteer)->launch($this->browserOptions);
  23. }
  24. }
  25. public function tearDown(): void
  26. {
  27. if (isset($this->browser)) {
  28. $this->browser->close();
  29. }
  30. $this->servingProcess->stop(0);
  31. }
  32. /** @test */
  33. public function can_browse_website()
  34. {
  35. $response = $this->browser->newPage()->goto($this->url);
  36. $this->assertTrue($response->ok(), 'Failed asserting that the response is successful.');
  37. }
  38. /**
  39. * @test
  40. */
  41. public function can_use_method_aliases()
  42. {
  43. $page = $this->browser->newPage();
  44. $page->goto($this->url);
  45. $select = function($resource) {
  46. $elements = [
  47. $resource->querySelector('h1'),
  48. $resource->querySelectorAll('h1')[0],
  49. $resource->querySelectorXPath('/html/body/h1')[0],
  50. ];
  51. $this->assertContainsOnlyInstancesOf(ElementHandle::class, $elements);
  52. };
  53. $evaluate = function($resource) {
  54. $strings = [
  55. $resource->querySelectorEval('h1', JsFunction::createWithBody('return "Hello World!";')),
  56. $resource->querySelectorAllEval('h1', JsFunction::createWithBody('return "Hello World!";')),
  57. ];
  58. foreach ($strings as $string) {
  59. $this->assertEquals('Hello World!', $string);
  60. }
  61. };
  62. // Test method aliases for Page, Frame and ElementHandle classes
  63. $resources = [$page, $page->mainFrame(), $page->querySelector('body')];
  64. foreach ($resources as $resource) {
  65. $select($resource);
  66. $evaluate($resource);
  67. }
  68. }
  69. /** @test */
  70. public function can_evaluate_a_selection()
  71. {
  72. $page = $this->browser->newPage();
  73. $page->goto($this->url);
  74. $title = $page->querySelectorEval('h1', JsFunction::createWithParameters(['node'])
  75. ->body('return node.textContent;'));
  76. $titleCount = $page->querySelectorAllEval('h1', JsFunction::createWithParameters(['nodes'])
  77. ->body('return nodes.length;'));
  78. $this->assertEquals('Example Page', $title);
  79. $this->assertEquals(1, $titleCount);
  80. }
  81. /** @test */
  82. public function can_intercept_requests()
  83. {
  84. $page = $this->browser->newPage();
  85. $page->setRequestInterception(true);
  86. $page->on('request', JsFunction::createWithParameters(['request'])
  87. ->body('request.resourceType() === "stylesheet" ? request.abort() : request.continue()'));
  88. $page->goto($this->url);
  89. $backgroundColor = $page->querySelectorEval('h1', JsFunction::createWithParameters(['node'])
  90. ->body('return getComputedStyle(node).textTransform'));
  91. $this->assertNotEquals('lowercase', $backgroundColor);
  92. }
  93. /**
  94. * @test
  95. * @dontPopulateProperties browser
  96. */
  97. public function check_all_resources_are_supported()
  98. {
  99. $incompleteResources = [];
  100. $resourceInstanciator = new ResourceInstanciator($this->browserOptions, $this->url);
  101. foreach ($resourceInstanciator->getResourceNames() as $name) {
  102. $resource = $resourceInstanciator->{$name}(new Puppeteer, $this->browserOptions);
  103. if ($resource instanceof UntestableResource) {
  104. $incompleteResources[$name] = $resource;
  105. } else if ($resource instanceof RiskyResource) {
  106. if (!empty($resource->exception())) {
  107. $incompleteResources[$name] = $resource;
  108. } else {
  109. try {
  110. $this->assertInstanceOf("Nesk\\Puphpeteer\\Resources\\$name", $resource->value());
  111. } catch (ExpectationFailedException $exception) {
  112. $incompleteResources[$name] = $resource;
  113. }
  114. }
  115. } else {
  116. $this->assertInstanceOf("Nesk\\Puphpeteer\\Resources\\$name", $resource);
  117. }
  118. }
  119. if (empty($incompleteResources)) return;
  120. $incompleteText = "The following resources have not been tested properly, probably"
  121. ." for good reasons but you might want to have a look:";
  122. foreach ($incompleteResources as $name => $resource) {
  123. if ($resource instanceof UntestableResource) {
  124. $reason = "Marked as untestable";
  125. } else if ($resource instanceof RiskyResource) {
  126. if (!empty($exception = $resource->exception())) {
  127. $reason = "Marked as risky because of a Node error: {$exception->getMessage()}";
  128. } else {
  129. $value = print_r($resource->value(), true);
  130. $reason = "Marked as risky because of an unexpected value: $value";
  131. }
  132. } else {
  133. $reason = "Unknow reason";
  134. }
  135. $incompleteText .= "\n • $name - $reason";
  136. }
  137. $this->markTestIncomplete($incompleteText);
  138. }
  139. /**
  140. * @test
  141. * @dontPopulateProperties browser
  142. */
  143. public function browser_console_calls_are_logged()
  144. {
  145. $setups = [
  146. [false, function ($browser) { return $browser->newPage(); }, 'Received data from the port'],
  147. [true, function ($browser) { return $browser->newPage(); }, 'Received a Browser log:'],
  148. [true, function ($browser) { return $browser->pages()[0]; }, 'Received a Browser log:'],
  149. ];
  150. foreach ($setups as [$shoulLogBrowserConsole, $pageFactory, $startsWith]) {
  151. $puppeteer = new Puppeteer([
  152. 'log_browser_console' => $shoulLogBrowserConsole,
  153. 'logger' => $this->loggerMock(
  154. $this->at(9),
  155. $this->isLogLevel(),
  156. $this->stringStartsWith($startsWith)
  157. ),
  158. ]);
  159. $this->browser = $puppeteer->launch($this->browserOptions);
  160. $pageFactory($this->browser)->goto($this->url);
  161. }
  162. }
  163. }