| Current Path : /var/www/www.indacotrentino.com/www/vendor/codeception/codeception/src/Codeception/ |
| Current File : /var/www/www.indacotrentino.com/www/vendor/codeception/codeception/src/Codeception/Step.php |
<?php
declare(strict_types=1);
namespace Codeception;
use Closure;
use Codeception\Lib\ModuleContainer;
use Codeception\Step\Argument\FormattedOutput;
use Codeception\Step\Meta as MetaStep;
use Codeception\Util\Locator;
use Exception;
use PHPUnit\Framework\Constraint\Constraint;
use PHPUnit\Framework\MockObject\MockObject;
use RuntimeException;
use Stringable;
abstract class Step implements Stringable
{
/**
* @var int
*/
public const DEFAULT_MAX_LENGTH = 200;
/**
* @var int
*/
public const STACK_POSITION = 3;
public bool $executed = false;
protected string|int|null $line = null;
protected ?string $file = null;
protected string $prefix = 'I';
protected ?MetaStep $metaStep = null;
protected bool $failed = false;
protected bool $isTry = false;
public function __construct(protected string $action, protected array $arguments = [])
{
}
public function saveTrace(): void
{
$stack = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
if (count($stack) <= self::STACK_POSITION) {
return;
}
$traceLine = $stack[self::STACK_POSITION - 1];
if (!isset($traceLine['file'])) {
return;
}
$this->file = $traceLine['file'];
$this->line = $traceLine['line'];
$this->addMetaStep($traceLine, $stack);
}
private function isTestFile(string $file)
{
return preg_match('#[^\\' . DIRECTORY_SEPARATOR . '](Cest|Cept|Test).php$#', $file);
}
public function getName(): string
{
$class = explode('\\', __CLASS__);
return end($class);
}
public function getAction(): string
{
return $this->action;
}
public function getFilePath(): ?string
{
if ($this->file) {
return codecept_relative_path($this->file);
}
return null;
}
public function getLineNumber(): ?int
{
if ($this->line) {
return $this->line;
}
return null;
}
public function hasFailed(): bool
{
return $this->failed;
}
public function getArguments(): array
{
return $this->arguments;
}
public function getArgumentsAsString(int $maxLength = self::DEFAULT_MAX_LENGTH): string
{
$arguments = $this->arguments;
$argumentCount = count($arguments);
$totalLength = $argumentCount - 1; // count separators before adding length of individual arguments
foreach ($arguments as $key => $argument) {
$stringifiedArgument = $this->stringifyArgument($argument);
$arguments[$key] = $stringifiedArgument;
$totalLength += mb_strlen($stringifiedArgument, 'utf-8');
}
if ($totalLength > $maxLength && $maxLength > 0) {
//sort arguments from shortest to longest
uasort($arguments, function ($arg1, $arg2): int {
$length1 = mb_strlen($arg1, 'utf-8');
$length2 = mb_strlen($arg2, 'utf-8');
if ($length1 === $length2) {
return 0;
}
return ($length1 < $length2) ? -1 : 1;
});
$allowedLength = floor(($maxLength - $argumentCount + 1) / $argumentCount);
$lengthRemaining = $maxLength;
$argumentsRemaining = $argumentCount;
foreach ($arguments as $key => $argument) {
--$argumentsRemaining;
if (mb_strlen($argument, 'utf-8') > $allowedLength) {
$arguments[$key] = mb_substr($argument, 0, (int)$allowedLength - 4, 'utf-8') . '...' . mb_substr($argument, -1, 1, 'utf-8');
$lengthRemaining -= ($allowedLength + 1);
} else {
$lengthRemaining -= (mb_strlen($argument, 'utf-8') + 1);
//recalculate allowed length because this argument was short
if ($argumentsRemaining > 0) {
$allowedLength = floor(($lengthRemaining - $argumentsRemaining + 1) / $argumentsRemaining);
}
}
}
//restore original order of arguments
ksort($arguments);
}
return implode(',', $arguments);
}
protected function stringifyArgument(mixed $argument): string
{
if (is_string($argument)) {
return '"' . strtr($argument, ["\n" => '\n', "\r" => '\r', "\t" => ' ']) . '"';
} elseif (is_resource($argument)) {
$argument = (string)$argument;
} elseif (is_array($argument)) {
foreach ($argument as $key => $value) {
if (is_object($value)) {
$argument[$key] = $this->getClassName($value);
}
}
} elseif (is_object($argument)) {
if ($argument instanceof FormattedOutput) {
$argument = $argument->getOutput();
} elseif (method_exists($argument, '__toString')) {
$argument = (string)$argument;
} elseif ($argument::class === 'Facebook\WebDriver\WebDriverBy') {
$argument = Locator::humanReadableString($argument);
} elseif ($argument instanceof Constraint) {
$argument = $argument->toString();
} else {
$argument = $this->getClassName($argument);
}
}
$arg_str = json_encode($argument, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
return str_replace('\"', '"', $arg_str);
}
protected function getClassName(object $argument): string
{
if ($argument instanceof Closure) {
return Closure::class;
} elseif ($argument instanceof MockObject) {
$parentClass = get_parent_class($argument);
$reflection = new \ReflectionClass($argument);
if ($parentClass !== false) {
return $this->formatClassName($parentClass);
}
$interfaces = $reflection->getInterfaceNames();
foreach ($interfaces as $interface) {
if (str_starts_with($interface, 'PHPUnit\\')) {
continue;
}
if (str_starts_with($interface, 'Codeception\\')) {
continue;
}
return $this->formatClassName($interface);
}
}
return $this->formatClassName($argument::class);
}
protected function formatClassName(string $classname): string
{
return trim($classname, "\\");
}
public function getPhpCode(int $maxLength): string
{
$result = "\${$this->prefix}->" . $this->getAction() . '(';
$maxLength = $maxLength - mb_strlen($result, 'utf-8') - 1;
return $result . ($this->getHumanizedArguments($maxLength) . ')');
}
public function getMetaStep(): ?MetaStep
{
return $this->metaStep;
}
public function __toString(): string
{
$humanizedAction = $this->humanize($this->getAction());
return $humanizedAction . ' ' . $this->getHumanizedArguments();
}
public function toString(int $maxLength): string
{
$humanizedAction = $this->humanize($this->getAction());
$maxLength = $maxLength - mb_strlen($humanizedAction, 'utf-8') - 1;
return $humanizedAction . ' ' . $this->getHumanizedArguments($maxLength);
}
public function getHtml(string $highlightColor = '#732E81'): string
{
if (empty($this->arguments)) {
return sprintf('%s %s', ucfirst($this->prefix), $this->humanize($this->getAction()));
}
return sprintf('%s %s <span style="color: %s">%s</span>', ucfirst($this->prefix), htmlspecialchars($this->humanize($this->getAction()), ENT_QUOTES | ENT_SUBSTITUTE), $highlightColor, htmlspecialchars($this->getHumanizedArguments(0), ENT_QUOTES | ENT_SUBSTITUTE));
}
public function getHumanizedActionWithoutArguments(): string
{
return $this->humanize($this->getAction());
}
public function getHumanizedArguments(int $maxLength = self::DEFAULT_MAX_LENGTH): string
{
return $this->getArgumentsAsString($maxLength);
}
protected function clean(string $text): string
{
return str_replace('\/', '', $text);
}
protected function humanize(string $text): string
{
$text = preg_replace('#([A-Z]+)([A-Z][a-z])#', '\\1 \\2', $text);
$text = preg_replace('#([a-z\d])([A-Z])#', '\\1 \\2', $text);
$text = preg_replace('#\bdont\b#', "don't", $text);
return mb_strtolower($text, 'UTF-8');
}
/**
* @return mixed
*/
public function run(ModuleContainer $container = null)
{
$this->executed = true;
if ($container === null) {
return null;
}
$activeModule = $container->moduleForAction($this->action);
if (!is_callable([$activeModule, $this->action])) {
throw new RuntimeException("Action '{$this->action}' can't be called");
}
try {
$res = call_user_func_array([$activeModule, $this->action], $this->arguments);
} catch (Exception $e) {
if ($this->isTry) {
throw $e;
}
$this->failed = true;
$this->getMetaStep()?->setFailed(true);
throw $e;
}
return $res;
}
/**
* If steps are combined into one method they can be reproduced as meta-step.
* We are using stack trace to analyze if steps were called from test, if not - they were called from meta-step.
*/
protected function addMetaStep(array $step, array $stack): void
{
if (($this->isTestFile($this->file)) || ($step['class'] == Scenario::class)) {
return;
}
$i = count($stack) - self::STACK_POSITION - 1;
// get into test file and retrieve its actual call
while (isset($stack[$i])) {
$step = $stack[$i];
--$i;
if (!isset($step['file']) || !isset($step['function']) || !isset($step['class'])) {
continue;
}
if (!$this->isTestFile($step['file'])) {
continue;
}
// in case arguments were passed by reference, copy args array to ensure dereference. array_values() does not dereference values
$this->metaStep = new Step\Meta($step['function'], array_map(fn ($i) => $i, array_values($step['args'])));
$this->metaStep->setTraceInfo($step['file'], $step['line']);
// page objects or other classes should not be included with "I"
if (!in_array(Actor::class, class_parents($step['class']))) {
if (isset($step['object'])) {
$this->metaStep->setPrefix($step['object']::class . ':');
return;
}
$this->metaStep->setPrefix($step['class'] . ':');
}
return;
}
}
public function setMetaStep(?MetaStep $metaStep): void
{
$this->metaStep = $metaStep;
}
public function getPrefix(): string
{
return $this->prefix . ' ';
}
}