mirror of
https://github.com/moodle/moodle.git
synced 2025-01-17 13:38:32 +01:00
MDL-80907 behat: be more precise in selecting table rows
This commit is contained in:
parent
f88dbfcafc
commit
569d9dfe39
@ -1423,31 +1423,8 @@ EOF;
|
||||
|
||||
$rowliteral = behat_context_helper::escape($row);
|
||||
$valueliteral = behat_context_helper::escape($value);
|
||||
$columnliteral = behat_context_helper::escape($column);
|
||||
|
||||
if (preg_match('/^-?(\d+)-?$/', $column, $columnasnumber)) {
|
||||
// Column indicated as a number, just use it as position of the column.
|
||||
$columnpositionxpath = "/child::*[position() = {$columnasnumber[1]}]";
|
||||
} else {
|
||||
// Header can be in thead or tbody (first row), following xpath should work.
|
||||
$theadheaderxpath = "thead/tr[1]/th[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" .
|
||||
$columnliteral . "] or div[normalize-space(text())=" . $columnliteral . "])]";
|
||||
$tbodyheaderxpath = "tbody/tr[1]/td[(normalize-space(.)=" . $columnliteral . " or a[normalize-space(text())=" .
|
||||
$columnliteral . "] or div[normalize-space(text())=" . $columnliteral . "])]";
|
||||
|
||||
// Check if column exists.
|
||||
$columnheaderxpath = $tablexpath . "[" . $theadheaderxpath . " | " . $tbodyheaderxpath . "]";
|
||||
$columnheader = $this->getSession()->getDriver()->find($columnheaderxpath);
|
||||
if (empty($columnheader)) {
|
||||
$columnexceptionmsg = $column . '" in table "' . $table . '"';
|
||||
throw new ElementNotFoundException($this->getSession(), "\n$columnheaderxpath\n\n".'Column', null, $columnexceptionmsg);
|
||||
}
|
||||
// Following conditions were considered before finding column count.
|
||||
// 1. Table header can be in thead/tr/th or tbody/tr/td[1].
|
||||
// 2. First column can have th (Gradebook -> user report), so having lenient sibling check.
|
||||
$columnpositionxpath = "/child::*[position() = count(" . $tablexpath . "/" . $theadheaderxpath .
|
||||
"/preceding-sibling::*) + 1]";
|
||||
}
|
||||
$columnpositionxpath = $this->get_table_column_xpath($table, $column);
|
||||
|
||||
// Check if value exists in specific row/column.
|
||||
// Get row xpath.
|
||||
@ -1489,6 +1466,99 @@ EOF;
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get xpath for a row child that corresponds to the specified column header
|
||||
*
|
||||
* @param string $table table identifier that can be used with 'table' node selector (i.e. table title or CSS class)
|
||||
* @param string $column either text in the column header or the column number, such as -1-, -2-, etc
|
||||
* When matching the column header it has to be either exact match of the whole header or an exact
|
||||
* match of a text inside a link in the header.
|
||||
* For example, to match "<a>First name</a> / <a>Last name</a>" you need to specify either "First name" or "Last name"
|
||||
* @return string
|
||||
*/
|
||||
protected function get_table_column_xpath(string $table, string $column): string {
|
||||
$tablenode = $this->get_selected_node('table', $table);
|
||||
$tablexpath = $tablenode->getXpath();
|
||||
$columnliteral = behat_context_helper::escape($column);
|
||||
if (preg_match('/^-?(\d+)-?$/', $column, $columnasnumber)) {
|
||||
// Column indicated as a number, just use it as position of the column.
|
||||
$columnpositionxpath = "/child::*[position() = {$columnasnumber[1]}]";
|
||||
} else {
|
||||
// Header can be in thead or tbody (first row), following xpath should work.
|
||||
$theadheaderxpath = "thead/tr[1]/th[(normalize-space(.)={$columnliteral} or a[normalize-space(text())=" .
|
||||
$columnliteral . "] or div[normalize-space(text())={$columnliteral}])]";
|
||||
$tbodyheaderxpath = "tbody/tr[1]/td[(normalize-space(.)={$columnliteral} or a[normalize-space(text())=" .
|
||||
$columnliteral . "] or div[normalize-space(text())={$columnliteral}])]";
|
||||
|
||||
// Check if column exists.
|
||||
$columnheaderxpath = "{$tablexpath}[{$theadheaderxpath} | {$tbodyheaderxpath}]";
|
||||
$columnheader = $this->getSession()->getDriver()->find($columnheaderxpath);
|
||||
if (empty($columnheader)) {
|
||||
if (strpos($column, '/') !== false) {
|
||||
// We are not able to match headers consisting of several links, such as "First name / Last name".
|
||||
// Instead we can match "First name" or "Last name" or "-1-" (column number).
|
||||
throw new Exception("Column matching locator \"$column\" not found. ".
|
||||
"If the column header contains multiple links, specify only one of the link texts. ".
|
||||
"Otherwise, use the column number as the locator");
|
||||
}
|
||||
$columnexceptionmsg = $column . '" in table "' . $table . '"';
|
||||
throw new ElementNotFoundException($this->getSession(), "\n$columnheaderxpath\n\n".'Column',
|
||||
null, $columnexceptionmsg);
|
||||
}
|
||||
// Following conditions were considered before finding column count.
|
||||
// 1. Table header can be in thead/tr/th or tbody/tr/td[1].
|
||||
// 2. First column can have th (Gradebook -> user report), so having lenient sibling check.
|
||||
$columnpositionxpath = "/child::*[position() = count({$tablexpath}/{$theadheaderxpath}" .
|
||||
"/preceding-sibling::*) + 1]";
|
||||
}
|
||||
return $columnpositionxpath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a table row where each of the specified columns matches and throw exception if not found
|
||||
*
|
||||
* @param string $table table locator
|
||||
* @param array $cells key is the column locator (name or index such as '-1-') and value is the text contents of the table cell
|
||||
*/
|
||||
protected function ensure_table_row_exists(string $table, array $cells): void {
|
||||
$tablenode = $this->get_selected_node('table', $table);
|
||||
$tablexpath = $tablenode->getXpath();
|
||||
|
||||
$columnconditions = [];
|
||||
foreach ($cells as $columnname => $value) {
|
||||
$valueliteral = behat_context_helper::escape($value);
|
||||
$columnpositionxpath = $this->get_table_column_xpath($table, $columnname);
|
||||
$columnconditions[] = '.' . $columnpositionxpath . "[contains(normalize-space(.)," . $valueliteral . ")]";
|
||||
}
|
||||
$rowxpath = $tablexpath . "/tbody/tr[" . join(' and ', $columnconditions) . ']';
|
||||
|
||||
$rownode = $this->getSession()->getDriver()->find($rowxpath);
|
||||
if (empty($rownode)) {
|
||||
$rowlocator = array_map(fn($k) => "{$k} => {$cells[$k]}", array_keys($cells));
|
||||
throw new ElementNotFoundException($this->getSession(), "\n$rowxpath\n\n".'Table row', null, join(', ', $rowlocator));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a table row where each of the specified columns matches and throw exception if found
|
||||
*
|
||||
* @param string $table table locator
|
||||
* @param array $cells key is the column locator (name or index such as '-1-') and value is the text contents of the table cell
|
||||
*/
|
||||
protected function ensure_table_row_does_not_exist(string $table, array $cells): void {
|
||||
try {
|
||||
$this->ensure_table_row_exists($table, $cells);
|
||||
// Throw exception if found.
|
||||
} catch (ElementNotFoundException $e) {
|
||||
// Table row/column doesn't contain this value. Nothing to do.
|
||||
return;
|
||||
}
|
||||
$rowlocator = array_map(fn($k) => "{$k} => {$cells[$k]}", array_keys($cells));
|
||||
throw new ExpectationException('Table row "' . join(', ', $rowlocator) .
|
||||
'" is present in the table "' . $table . '"', $this->getSession()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the provided value exist in table.
|
||||
*
|
||||
@ -1505,29 +1575,22 @@ EOF;
|
||||
*/
|
||||
public function following_should_exist_in_the_table($table, TableNode $data) {
|
||||
$datahash = $data->getHash();
|
||||
if ($datahash && count($data->getRow(0)) != count($datahash[0])) {
|
||||
// Check that the number of columns in the hash is the same as the number of the columns in the first row.
|
||||
throw new coding_exception('Table contains duplicate column headers');
|
||||
}
|
||||
|
||||
foreach ($datahash as $row) {
|
||||
|
||||
// Row contains only a single column, just assert it's present in the table.
|
||||
if (count($row) === 1) {
|
||||
$this->execute('behat_general::assert_element_contains_text', [reset($row), $table, 'table']);
|
||||
} else {
|
||||
// Iterate over all columns.
|
||||
$firstcell = null;
|
||||
foreach ($row as $column => $value) {
|
||||
if ($firstcell === null) {
|
||||
$firstcell = $value;
|
||||
} else {
|
||||
$this->row_column_of_table_should_contain($firstcell, $column, $table, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->ensure_table_row_exists($table, $row);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the provided values do not exist in a table.
|
||||
*
|
||||
* If there are more than two columns, we check that NEITHER of the columns 2..n match
|
||||
* in the row where the first column matches
|
||||
*
|
||||
* @Then /^the following should not exist in the "(?P<table_string>[^"]*)" table:$/
|
||||
* @throws ExpectationException
|
||||
* @param string $table name of table
|
||||
@ -1537,27 +1600,24 @@ EOF;
|
||||
*/
|
||||
public function following_should_not_exist_in_the_table($table, TableNode $data) {
|
||||
$datahash = $data->getHash();
|
||||
if ($datahash && count($data->getRow(0)) != count($datahash[0])) {
|
||||
// Check that the number of columns in the hash is the same as the number of the columns in the first row.
|
||||
throw new coding_exception('Table contains duplicate column headers');
|
||||
}
|
||||
|
||||
foreach ($datahash as $value) {
|
||||
|
||||
// Row contains only a single column, just assert it's not present in the table.
|
||||
if (count($value) === 1) {
|
||||
$this->execute('behat_general::assert_element_not_contains_text', [reset($value), $table, 'table']);
|
||||
} else {
|
||||
// Iterate over all columns.
|
||||
$row = array_shift($value);
|
||||
foreach ($value as $column => $value) {
|
||||
try {
|
||||
$this->row_column_of_table_should_contain($row, $column, $table, $value);
|
||||
// Throw exception if found.
|
||||
} catch (ElementNotFoundException $e) {
|
||||
// Table row/column doesn't contain this value. Nothing to do.
|
||||
continue;
|
||||
}
|
||||
throw new ExpectationException('"' . $column . '" with value "' . $value . '" is present in "' .
|
||||
$row . '" row for table "' . $table . '"', $this->getSession()
|
||||
);
|
||||
if (count($value) > 2) {
|
||||
// When there are more than two columns, what we really want to check is that for the rows
|
||||
// where the first column matches, NEITHER of the other columns match.
|
||||
$columns = array_keys($value);
|
||||
for ($i = 1; $i < count($columns); $i++) {
|
||||
$this->ensure_table_row_does_not_exist($table, [
|
||||
$columns[0] => $value[$columns[0]],
|
||||
$columns[$i] => $value[$columns[$i]],
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
$this->ensure_table_row_does_not_exist($table, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user