1<?php
2
3namespace Drupal\FunctionalJavascriptTests;
4
5use PHPUnit\Framework\AssertionFailedError;
6
7/**
8 * Tests if we can execute JavaScript in the browser.
9 *
10 * @group javascript
11 */
12class BrowserWithJavascriptTest extends WebDriverTestBase {
13
14  /**
15   * Modules to enable.
16   *
17   * @var array
18   */
19  protected static $modules = ['test_page_test'];
20
21  /**
22   * {@inheritdoc}
23   */
24  protected $defaultTheme = 'stark';
25
26  public function testJavascript() {
27    $this->drupalGet('<front>');
28    $session = $this->getSession();
29
30    $session->resizeWindow(400, 300);
31    $javascript = <<<JS
32    (function(){
33        var w = window,
34        d = document,
35        e = d.documentElement,
36        g = d.getElementsByTagName('body')[0],
37        x = w.innerWidth || e.clientWidth || g.clientWidth,
38        y = w.innerHeight || e.clientHeight|| g.clientHeight;
39        return x == 400 && y == 300;
40    }())
41JS;
42    $this->assertJsCondition($javascript);
43
44    // Ensure that \Drupal\Tests\UiHelperTrait::isTestUsingGuzzleClient() works
45    // as expected.
46    $this->assertFalse($this->isTestUsingGuzzleClient());
47  }
48
49  public function testAssertJsCondition() {
50    $this->drupalGet('<front>');
51    $session = $this->getSession();
52
53    $session->resizeWindow(500, 300);
54    $javascript = <<<JS
55    (function(){
56        var w = window,
57        d = document,
58        e = d.documentElement,
59        g = d.getElementsByTagName('body')[0],
60        x = w.innerWidth || e.clientWidth || g.clientWidth,
61        y = w.innerHeight || e.clientHeight|| g.clientHeight;
62        return x == 400 && y == 300;
63    }())
64JS;
65
66    // We expected the following assertion to fail because the window has been
67    // re-sized to have a width of 500 not 400.
68    $this->expectException(AssertionFailedError::class);
69    $this->assertJsCondition($javascript, 100);
70  }
71
72  /**
73   * Tests creating screenshots.
74   */
75  public function testCreateScreenshot() {
76    $this->drupalGet('<front>');
77    $this->createScreenshot('public://screenshot.jpg');
78    $this->assertFileExists('public://screenshot.jpg');
79  }
80
81  /**
82   * Tests assertEscaped() and assertUnescaped().
83   *
84   * @see \Drupal\Tests\WebAssert::assertNoEscaped()
85   * @see \Drupal\Tests\WebAssert::assertEscaped()
86   */
87  public function testEscapingAssertions() {
88    $assert = $this->assertSession();
89
90    $this->drupalGet('test-escaped-characters');
91    $assert->assertNoEscaped('<div class="escaped">');
92    $assert->responseContains('<div class="escaped">');
93    $assert->assertEscaped('Escaped: <"\'&>');
94
95    $this->drupalGet('test-escaped-script');
96    $assert->assertNoEscaped('<div class="escaped">');
97    $assert->responseContains('<div class="escaped">');
98    $assert->assertEscaped("<script>alert('XSS');alert(\"XSS\");</script>");
99
100    $this->drupalGetWithAlert('test-unescaped-script');
101    $assert->assertNoEscaped('<div class="unescaped">');
102    $assert->responseContains('<div class="unescaped">');
103    $assert->responseContains("<script>alert('Marked safe');alert(\"Marked safe\");</script>");
104    $assert->assertNoEscaped("<script>alert('Marked safe');alert(\"Marked safe\");</script>");
105  }
106
107  /**
108   * Retrieves a Drupal path or an absolute path.
109   *
110   * @param string|\Drupal\Core\Url $path
111   *   Drupal path or URL to load into Mink controlled browser.
112   * @param array $options
113   *   (optional) Options to be forwarded to the url generator.
114   * @param string[] $headers
115   *   An array containing additional HTTP request headers, the array keys are
116   *   the header names and the array values the header values. This is useful
117   *   to set for example the "Accept-Language" header for requesting the page
118   *   in a different language. Note that not all headers are supported, for
119   *   example the "Accept" header is always overridden by the browser. For
120   *   testing REST APIs it is recommended to obtain a separate HTTP client
121   *   using getHttpClient() and performing requests that way.
122   *
123   * @return string
124   *   The retrieved HTML string, also available as $this->getRawContent()
125   *
126   * @see \Drupal\Tests\BrowserTestBase::getHttpClient()
127   */
128  protected function drupalGetWithAlert($path, array $options = [], array $headers = []) {
129    $options['absolute'] = TRUE;
130    $url = $this->buildUrl($path, $options);
131
132    $session = $this->getSession();
133
134    $this->prepareRequest();
135    foreach ($headers as $header_name => $header_value) {
136      $session->setRequestHeader($header_name, $header_value);
137    }
138
139    $session->visit($url);
140
141    // There are 2 alerts to accept before we can get the content of the page.
142    $session->getDriver()->getWebdriverSession()->accept_alert();
143    $session->getDriver()->getWebdriverSession()->accept_alert();
144
145    $out = $session->getPage()->getContent();
146
147    // Ensure that any changes to variables in the other thread are picked up.
148    $this->refreshVariables();
149
150    // Replace original page output with new output from redirected page(s).
151    if ($new = $this->checkForMetaRefresh()) {
152      $out = $new;
153      // We are finished with all meta refresh redirects, so reset the counter.
154      $this->metaRefreshCount = 0;
155    }
156
157    // Log only for WebDriverTestBase tests because for DrupalTestBrowser we log
158    // with ::getResponseLogHandler.
159    if ($this->htmlOutputEnabled && !$this->isTestUsingGuzzleClient()) {
160      $html_output = 'GET request to: ' . $url .
161        '<hr />Ending URL: ' . $this->getSession()->getCurrentUrl();
162      $html_output .= '<hr />' . $out;
163      $html_output .= $this->getHtmlOutputHeaders();
164      $this->htmlOutput($html_output);
165    }
166
167    return $out;
168  }
169
170}
171