Skip to content

Commit

Permalink
Read and store URL of executor node also for tests run on Selenium 4
Browse files Browse the repository at this point in the history
  • Loading branch information
OndraM committed Jul 20, 2023
1 parent 6dbe58a commit a31ed93
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 31 deletions.
19 changes: 11 additions & 8 deletions src-tests/Publisher/XmlPublisherTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -302,10 +302,10 @@ public function testShouldProperlyHandleTestsWithDataProvider(string $testCaseNa
}

/**
* @dataProvider provideEndpointTestsessionResponse
* @dataProvider provideServerGraphQlResponse
*/
public function testShouldLogTestExecutorWhenTestStarted(
string $testsessionEndpointResponse,
string $serverResponse,
?string $expectedExecutor
): void {
$webDriverMock = $this->createMock(RemoteWebDriver::class);
Expand All @@ -317,9 +317,12 @@ public function testShouldLogTestExecutorWhenTestStarted(
$testMock->wd = $webDriverMock;

$fileGetContentsMock = $this->getFunctionMock('Lmc\Steward\Selenium', 'file_get_contents');
$fileGetContentsMock->expects($this->once())
->with('http://server.tld:4444/grid/api/testsession?session=session-id-foo-bar')
->willReturn($testsessionEndpointResponse);
$fileGetContentsMock->expects($this->at(0))
->with('http://server.tld:4444/graphql')
->willReturn('{"data": {"__typename": "GridQuery"}}');
$fileGetContentsMock->expects($this->at(1))
->with('http://server.tld:4444/graphql')
->willReturn($serverResponse);

$fileName = $this->createEmptyFile();

Expand All @@ -341,12 +344,12 @@ public function testShouldLogTestExecutorWhenTestStarted(
/**
* @return array[]
*/
public function provideEndpointTestsessionResponse(): array
public function provideServerGraphQlResponse(): array
{
return [
'executor found' => [
file_get_contents(__DIR__ . '/../Selenium/Fixtures/testsession-found.json'),
'http://10.1.255.241:5555',
file_get_contents(__DIR__ . '/../Selenium/Fixtures/graphql-response-found.json'),
'http://10.216.10.116:5555',
],
'empty response' => ['', null],
];
Expand Down
9 changes: 9 additions & 0 deletions src-tests/Selenium/Fixtures/graphql-response-found.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"data": {
"session": {
"id": "4f1bebc2-667e-4b99-b16a-ff36221a20b3",
"nodeId": "9ae55dd1-8dcd-4cbf-81fb-fc144ab3d90e",
"nodeUri": "http:\u002f\u002f10.216.10.116:5555"
}
}
}
1 change: 1 addition & 0 deletions src-tests/Selenium/Fixtures/graphql-response-invalid.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Invalid GraphQL response
21 changes: 21 additions & 0 deletions src-tests/Selenium/Fixtures/graphql-response-not-found.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"errors": [
{
"message": "Exception while fetching data (\u002fsession) : No ongoing session found with the requested session id.",
"locations": [
{
"line": 1,
"column": 3
}
],
"path": [
"session"
],
"extensions": {
"sessionId": "4f1bebc2-667e-4b99-b16a-ff36221a20b3",
"classification": "DataFetchingException"
}
}
],
"data": null
}
60 changes: 55 additions & 5 deletions src-tests/Selenium/SeleniumServerAdapterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,13 +216,21 @@ public function testShouldThrowExceptionWhenGettingCloudServiceNameButTheServerR
}

/**
* @dataProvider provideSessionExecutorResponse
* @dataProvider provideTestsessionResponse
*/
public function testShouldGetSessionExecutor(string $responseData, string $expectedSessionExecutor): void
{
public function testShouldGetSessionExecutorForSelenium3(
string $responseData,
string $expectedSessionExecutor
): void {
$adapter = new SeleniumServerAdapter('http://127.0.0.1:4444/wd/hub');
$fileGetContentsMock = $this->getFunctionMock(__NAMESPACE__, 'file_get_contents');
$fileGetContentsMock->expects($this->once())

// Selenium 3 does not provide GraphQL API
$fileGetContentsMock->expects($this->at(0))
->with('http://127.0.0.1:4444/graphql')
->willReturn(false);

$fileGetContentsMock->expects($this->at(1))
->with('http://127.0.0.1:4444/grid/api/testsession?session=4f1bebc2-667e-4b99-b16a-ff36221a20b3')
->willReturn($responseData);

Expand All @@ -235,7 +243,7 @@ public function testShouldGetSessionExecutor(string $responseData, string $expec
/**
* @return array[]
*/
public function provideSessionExecutorResponse(): array
public function provideTestsessionResponse(): array
{
$responseExecutorFound = file_get_contents(__DIR__ . '/Fixtures/testsession-found.json');
$responseExecutorNotFound = file_get_contents(__DIR__ . '/Fixtures/testsession-not-found.json');
Expand All @@ -249,4 +257,46 @@ public function provideSessionExecutorResponse(): array
'Empty response' => ['', ''],
];
}

/**
* @dataProvider provideGraphQlResponse
*/
public function testShouldGetSessionExecutorForSelenium4(
string $responseData,
string $expectedSessionExecutor
): void {
$adapter = new SeleniumServerAdapter('http://127.0.0.1:4444/wd/hub');
$fileGetContentsMock = $this->getFunctionMock(__NAMESPACE__, 'file_get_contents');

$fileGetContentsMock->expects($this->at(0))
->with('http://127.0.0.1:4444/graphql')
->willReturn('{"data": {"__typename": "GridQuery"}}');

$fileGetContentsMock->expects($this->at(1))
->with('http://127.0.0.1:4444/graphql')
->willReturn($responseData);

$this->assertSame(
$expectedSessionExecutor,
$adapter->getSessionExecutor('4f1bebc2-667e-4b99-b16a-ff36221a20b3')
);
}

/**
* @return array[]
*/
public function provideGraphQlResponse(): array
{
$responseExecutorFound = file_get_contents(__DIR__ . '/Fixtures/graphql-response-found.json');
$responseExecutorNotFound = file_get_contents(__DIR__ . '/Fixtures/graphql-response-not-found.json');
$responseInvalid = file_get_contents(__DIR__ . '/Fixtures/graphql-response-invalid.txt');

return [
// $responseData, $expectedSessionExecutor
'Executor for session was found' => [$responseExecutorFound, 'http://10.216.10.116:5555'],
'Executor for session was not found' => [$responseExecutorNotFound, ''],
'Invalid response by API to get session information' => [$responseInvalid, ''],
'Empty response' => ['', ''],
];
}
}
2 changes: 1 addition & 1 deletion src/Exception/CommandException.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use Symfony\Component\Console\Exception\ExceptionInterface;

/**
* Runtime exception from Steward Command (ie. wrong CLI arguments).
* Runtime exception from Steward Command (e.g. wrong CLI arguments).
* It also implements `Symfony\Component\Console\Exception\ExceptionInterface` to not break Symfony Command behavior.
*/
class CommandException extends \RuntimeException implements ExceptionInterface, StewardExceptionInterface
Expand Down
93 changes: 76 additions & 17 deletions src/Selenium/SeleniumServerAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class SeleniumServerAdapter

protected const STATUS_ENDPOINT = '/status';
protected const TESTSESSION_ENDPOINT = '/grid/api/testsession';
protected const GRAPHQL_ENDPOINT = '/graphql';
protected const HUB_ENDPOINT = '/wd/hub';
protected const DEFAULT_PORT = 4444;
protected const DEFAULT_PORT_CLOUD_SERVICE = 80;
Expand Down Expand Up @@ -60,7 +61,7 @@ public function getServerUrl(): string
}

/**
* Test if server URL is accessible and we can connected there
* Test if server URL is accessible and we can connect there
*/
public function isAccessible(): bool
{
Expand Down Expand Up @@ -134,29 +135,30 @@ public function getCloudService(): string
*/
public function getSessionExecutor(string $sessionId): string
{
$context = stream_context_create(['http' => ['ignore_errors' => true, 'timeout' => 1]]);
if ($this->isGraphQlCapable()) {
try {
$response = $this->graphQlQuery(sprintf('{ session (id: "%s") { id, nodeId, nodeUri } }', $sessionId));
} catch (\RuntimeException $e) {
return '';
}

// Selenium server URL ends with /wd/hub. But the endpoint to get the session info is available on URL
// which starts from the same root path as the /wd/hub - thus we must remove this path.
$endpointUrl = $this->removeHubEndpointPathIfPresent($this->getServerUrl()) . self::TESTSESSION_ENDPOINT;
return $response['data']['session']['nodeUri'] ?? '';
}

$responseData = @file_get_contents($endpointUrl . '?session=' . $sessionId, false, $context);
return $this->getSessionExecutorForSelenium3($sessionId);
}

if (!$responseData) {
return '';
}
private function isGraphQlCapable(): bool
{
$query = '{__typename}'; // simple GraphQL health check to query top level typename

try {
$responseJson = json_decode($responseData, false, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
return '';
}

if (empty($responseJson->proxyId)) {
return '';
$this->graphQlQuery($query);
} catch (\Throwable $e) {
return false;
}

return $responseJson->proxyId;
return true;
}

/**
Expand Down Expand Up @@ -223,4 +225,61 @@ private function detectCloudServiceByStatus($responseData): string

return '';
}

private function getSessionExecutorForSelenium3(string $sessionId): string
{
$serverBaseUrl = $this->removeHubEndpointPathIfPresent($this->getServerUrl());
$context = stream_context_create(['http' => ['ignore_errors' => true, 'timeout' => 1]]);
$endpointUrl = $serverBaseUrl . self::TESTSESSION_ENDPOINT;
$responseData = @file_get_contents($endpointUrl . '?session=' . $sessionId, false, $context);

if (!$responseData) {
return '';
}

try {
$responseJson = json_decode($responseData, false, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $e) {
return '';
}

if (empty($responseJson->proxyId)) {
return '';
}

return $responseJson->proxyId;
}

private function graphQlQuery(string $query): array
{
$context = stream_context_create(
[
'http' => [
'method' => 'POST',
'header' => ['Content-Type: application/json'],
'content' => json_encode(['query' => $query]),
'timeout' => 5,
],
]
);

$responseData = @file_get_contents(
$this->removeHubEndpointPathIfPresent($this->getServerUrl()) . self::GRAPHQL_ENDPOINT,
false,
$context
);

if ($responseData === false) {
$error = error_get_last();
throw new \RuntimeException('Error executing GraphQL query: ' . $error['message'], $error['type']);
}

$parsedResponse = json_decode($responseData, true);

if (!is_array($parsedResponse)) {
throw new \RuntimeException('Error decoding GraphQL response');
}

return $parsedResponse;
}
}

0 comments on commit a31ed93

Please sign in to comment.