MOBILE-4061 behat: Use angular zone and improve blocking system
parent
a94da4d804
commit
136fc96711
|
@ -113,7 +113,7 @@ class behat_app extends behat_base {
|
|||
}
|
||||
|
||||
/**
|
||||
* Opens the Moodle app in the browser and introduces the enters the site.
|
||||
* Opens the Moodle App in the browser and introduces the enters the site.
|
||||
*
|
||||
* @Given /^I enter the app$/
|
||||
* @throws DriverException Issue with configuration or feature file
|
||||
|
@ -126,7 +126,7 @@ class behat_app extends behat_base {
|
|||
}
|
||||
|
||||
/**
|
||||
* Opens the Moodle app in the browser logged in as a user.
|
||||
* Opens the Moodle App in the browser logged in as a user.
|
||||
*
|
||||
* @Given /^I enter(ed)? the app as "(.+)"$/
|
||||
* @throws DriverException Issue with configuration or feature file
|
||||
|
@ -140,7 +140,7 @@ class behat_app extends behat_base {
|
|||
}
|
||||
|
||||
/**
|
||||
* Opens the Moodle app in the browser.
|
||||
* Opens the Moodle App in the browser.
|
||||
*
|
||||
* @Given /^I launch the app( runtime)?$/
|
||||
* @throws DriverException Issue with configuration or feature file
|
||||
|
@ -269,6 +269,8 @@ class behat_app extends behat_base {
|
|||
|
||||
$this->evaluate_script("window.behat.getAngularInstance('ion-content', 'CoreSwipeNavigationDirective').$method()");
|
||||
|
||||
$this->wait_for_pending_js();
|
||||
|
||||
// Wait swipe animation to finish.
|
||||
$this->getSession()->wait(300);
|
||||
}
|
||||
|
@ -327,7 +329,7 @@ class behat_app extends behat_base {
|
|||
}
|
||||
|
||||
/**
|
||||
* Fixes the Moodle admin settings to allow mobile app use (if not already correct).
|
||||
* Fixes the Moodle admin settings to allow Moodle App use (if not already correct).
|
||||
*
|
||||
* @throws dml_exception If there is any problem changing Moodle settings
|
||||
*/
|
||||
|
@ -517,7 +519,7 @@ class behat_app extends behat_base {
|
|||
}
|
||||
}
|
||||
|
||||
throw new DriverException('Moodle app not found in browser');
|
||||
throw new DriverException('Moodle App not found in browser');
|
||||
}, false, 60);
|
||||
|
||||
try {
|
||||
|
@ -529,7 +531,7 @@ class behat_app extends behat_base {
|
|||
|
||||
$this->execute_script('window.behatInit(' . json_encode($initOptions) . ');');
|
||||
} catch (Exception $error) {
|
||||
throw new DriverException('Moodle app not running or not running on Automated mode.');
|
||||
throw new DriverException('Moodle App not running or not running on Automated mode.');
|
||||
}
|
||||
|
||||
if ($restart) {
|
||||
|
@ -548,7 +550,7 @@ class behat_app extends behat_base {
|
|||
return true;
|
||||
}
|
||||
|
||||
throw new DriverException('Moodle app not launched properly');
|
||||
throw new DriverException('Moodle App not launched properly');
|
||||
}, false, 60);
|
||||
}
|
||||
|
||||
|
@ -591,7 +593,7 @@ class behat_app extends behat_base {
|
|||
if ($mainmenu) {
|
||||
return 'mainpage';
|
||||
}
|
||||
throw new DriverException('Moodle app main page not loaded after login');
|
||||
throw new DriverException('Moodle App main page not loaded after login');
|
||||
}, false, 30);
|
||||
|
||||
// Wait for JS to finish as well.
|
||||
|
@ -700,7 +702,7 @@ class behat_app extends behat_base {
|
|||
* @param TableNode $data
|
||||
*/
|
||||
public function i_open_a_custom_link(TableNode $data) {
|
||||
global $DB, $CFG;
|
||||
global $DB;
|
||||
|
||||
$data = $data->getColumnsHash()[0];
|
||||
$title = array_keys($data)[0];
|
||||
|
@ -713,10 +715,33 @@ class behat_app extends behat_base {
|
|||
|
||||
break;
|
||||
|
||||
case 'assign':
|
||||
case 'bigbluebuttonbn':
|
||||
case 'book':
|
||||
case 'chat':
|
||||
case 'choice':
|
||||
case 'data':
|
||||
case 'feedback':
|
||||
case 'folder':
|
||||
case 'forum':
|
||||
$forumdata = $DB->get_record('forum', ['name' => $data->forum]);
|
||||
$cm = get_coursemodule_from_instance('forum', $forumdata->id);
|
||||
$pageurl = "/mod/forum/view.php?id={$cm->id}";
|
||||
case 'glossary':
|
||||
case 'h5pactivity':
|
||||
case 'imscp':
|
||||
case 'label':
|
||||
case 'lesson':
|
||||
case 'lti':
|
||||
case 'page':
|
||||
case 'quiz':
|
||||
case 'resource':
|
||||
case 'scorm':
|
||||
case 'survey':
|
||||
case 'url':
|
||||
case 'wiki':
|
||||
case 'workshop':
|
||||
$name = $data->$title;
|
||||
$module = $DB->get_record($title, ['name' => $name]);
|
||||
$cm = get_coursemodule_from_instance($title, $module->id);
|
||||
$pageurl = "/mod/$title/view.php?id={$cm->id}";
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -913,8 +938,8 @@ class behat_app extends behat_base {
|
|||
*/
|
||||
public function the_app_should_have_opened_a_browser_tab(bool $not = false, ?string $urlpattern = null) {
|
||||
$this->spin(function() use ($not, $urlpattern) {
|
||||
$windownames = $this->getSession()->getWindowNames();
|
||||
$openedbrowsertab = count($windownames) === 2;
|
||||
$windowNames = $this->getSession()->getWindowNames();
|
||||
$openedbrowsertab = count($windowNames) === 2;
|
||||
|
||||
if ((!$not && !$openedbrowsertab) || ($not && $openedbrowsertab && is_null($urlpattern))) {
|
||||
throw new ExpectationException(
|
||||
|
@ -926,10 +951,10 @@ class behat_app extends behat_base {
|
|||
}
|
||||
|
||||
if (!is_null($urlpattern)) {
|
||||
$this->getSession()->switchToWindow($windownames[1]);
|
||||
$this->getSession()->switchToWindow($windowNames[1]);
|
||||
$windowurl = $this->getSession()->getCurrentUrl();
|
||||
$windowhaspattern = preg_match("/$urlpattern/", $windowurl);
|
||||
$this->getSession()->switchToWindow($windownames[0]);
|
||||
$this->getSession()->switchToWindow($windowNames[0]);
|
||||
|
||||
if ($not === $windowhaspattern) {
|
||||
throw new ExpectationException(
|
||||
|
@ -954,11 +979,11 @@ class behat_app extends behat_base {
|
|||
* @throws DriverException If there aren't exactly 2 tabs open
|
||||
*/
|
||||
public function i_switch_to_the_browser_tab_opened_by_the_app() {
|
||||
$names = $this->getSession()->getWindowNames();
|
||||
if (count($names) !== 2) {
|
||||
throw new DriverException('Expected to see 2 tabs open, not ' . count($names));
|
||||
$windowNames = $this->getSession()->getWindowNames();
|
||||
if (count($windowNames) !== 2) {
|
||||
throw new DriverException('Expected to see 2 tabs open, not ' . count($windowNames));
|
||||
}
|
||||
$this->getSession()->switchToWindow($names[1]);
|
||||
$this->getSession()->switchToWindow($windowNames[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -984,8 +1009,8 @@ class behat_app extends behat_base {
|
|||
new ExpectationException('Forced cron tasks in the app took too long to complete', $session)
|
||||
);
|
||||
|
||||
// Trigger Angular change detection
|
||||
$session->executeScript('ngZone.run(() => {});');
|
||||
// Trigger Angular change detection.
|
||||
$this->trigger_angular_change_detection();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -998,7 +1023,7 @@ class behat_app extends behat_base {
|
|||
|
||||
$this->spin(
|
||||
function() use ($session) {
|
||||
$session->executeScript('ngZone.run(() => {});');
|
||||
$this->trigger_angular_change_detection();
|
||||
|
||||
$nodes = $this->find_all('css', 'core-loading ion-spinner');
|
||||
|
||||
|
@ -1169,8 +1194,16 @@ class behat_app extends behat_base {
|
|||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Opens a custom URL for automatic login and redirect from the mobile app (and waits to finish.)
|
||||
* Trigger Angular change detection.
|
||||
*/
|
||||
private function trigger_angular_change_detection() {
|
||||
$this->getSession()->executeScript('ngZone.run(() => {});');
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a custom URL for automatic login and redirect from the Moodle App (and waits to finish.)
|
||||
*
|
||||
* @param string $username Of the user that needs to be logged in.
|
||||
* @param string $path To redirect the user.
|
||||
|
@ -1221,7 +1254,7 @@ class behat_app extends behat_base {
|
|||
}
|
||||
|
||||
/**
|
||||
* Opens a custom URL on the mobile app (and waits to finish.)
|
||||
* Opens a custom URL on the Moodle App (and waits to finish.)
|
||||
*
|
||||
* @param string $path To navigate.
|
||||
* @param string $successXPath The XPath of the element to lookat after navigation.
|
||||
|
@ -1236,7 +1269,7 @@ class behat_app extends behat_base {
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles the custom URL on the mobile app (and waits to finish.)
|
||||
* Handles the custom URL on the Moodle App (and waits to finish.)
|
||||
*
|
||||
* @param string $customurl To navigate.
|
||||
* @param string $successXPath The XPath of the element to lookat after navigation.
|
||||
|
@ -1245,6 +1278,8 @@ class behat_app extends behat_base {
|
|||
// Instead of using evaluate_async_script, we wait for the path to load.
|
||||
$this->evaluate_script("return window.behat.handleCustomURL('$customurl')");
|
||||
|
||||
$this->wait_for_pending_js();
|
||||
|
||||
if (!empty($successXPath)) {
|
||||
// Wait until the page appears.
|
||||
$this->spin(
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { makeSingleton, NgZone } from '@singletons';
|
||||
import { BehatTestsWindow, TestsBehatRuntime } from './behat-runtime';
|
||||
|
||||
/**
|
||||
|
@ -26,6 +27,7 @@ export class TestsBehatBlockingService {
|
|||
protected recentMutation = false;
|
||||
protected lastMutation = 0;
|
||||
protected initialized = false;
|
||||
protected keyIndex = 0;
|
||||
|
||||
/**
|
||||
* Listen to mutations and override XML Requests.
|
||||
|
@ -74,16 +76,23 @@ export class TestsBehatBlockingService {
|
|||
/**
|
||||
* Adds a pending key to the array.
|
||||
*
|
||||
* @param key Key to add.
|
||||
* @param key Key to add. It will be generated if none.
|
||||
* @return Key name.
|
||||
*/
|
||||
block(key: string): void {
|
||||
block(key = ''): string {
|
||||
// Add a special DELAY entry whenever another entry is added.
|
||||
if (this.pendingList.length === 0) {
|
||||
this.pendingList.push('DELAY');
|
||||
}
|
||||
if (!key) {
|
||||
key = 'generated-' + this.keyIndex;
|
||||
this.keyIndex++;
|
||||
}
|
||||
this.pendingList.push(key);
|
||||
|
||||
TestsBehatRuntime.log('PENDING+: ' + this.pendingList);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -92,7 +101,7 @@ export class TestsBehatBlockingService {
|
|||
*
|
||||
* @param key Key to remove
|
||||
*/
|
||||
unblock(key: string): void {
|
||||
async unblock(key: string): Promise<void> {
|
||||
// Remove the key immediately.
|
||||
this.pendingList = this.pendingList.filter((x) => x !== key);
|
||||
|
||||
|
@ -100,43 +109,32 @@ export class TestsBehatBlockingService {
|
|||
|
||||
// If the only thing left is DELAY, then remove that as well, later...
|
||||
if (this.pendingList.length === 1) {
|
||||
this.runAfterEverything(() => {
|
||||
// Check there isn't a spinner...
|
||||
this.checkUIBlocked();
|
||||
if (!document.hidden) {
|
||||
// When tab is not active, ticks should be slower and may do Behat to fail.
|
||||
// From Timers API:
|
||||
// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers
|
||||
// "This API does not guarantee that timers will run exactly on schedule.
|
||||
// Delays due to CPU load, other tasks, etc, are to be expected."
|
||||
await CoreUtils.nextTicks(10);
|
||||
}
|
||||
|
||||
// Only remove it if the pending array is STILL empty after all that.
|
||||
if (this.pendingList.length === 1) {
|
||||
this.pendingList = [];
|
||||
TestsBehatRuntime.log('PENDING-: ' + this.pendingList);
|
||||
}
|
||||
});
|
||||
// Check there isn't a spinner...
|
||||
await this.checkUIBlocked();
|
||||
|
||||
// Only remove it if the pending array is STILL empty after all that.
|
||||
if (this.pendingList.length === 1) {
|
||||
this.pendingList = [];
|
||||
TestsBehatRuntime.log('PENDING-: ' + this.pendingList);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a pending key to the array, but removes it after some setTimeouts finish.
|
||||
* Adds a pending key to the array, but removes it after some ticks.
|
||||
*/
|
||||
delay(): void {
|
||||
this.block('...');
|
||||
this.unblock('...');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run after several setTimeouts to ensure queued events are finished.
|
||||
*
|
||||
* @param target Function to run.
|
||||
* @param count Number of times to do setTimeout (leave blank for 10).
|
||||
*/
|
||||
protected runAfterEverything(target: () => void, count = 10): void {
|
||||
setTimeout(() => {
|
||||
count--;
|
||||
if (count === 0) {
|
||||
target();
|
||||
|
||||
return;
|
||||
}
|
||||
this.runAfterEverything(target, count);
|
||||
}, 0);
|
||||
async delay(): Promise<void> {
|
||||
const key = this.block('forced-delay');
|
||||
this.unblock(key);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -192,8 +190,9 @@ export class TestsBehatBlockingService {
|
|||
* Checks if a loading spinner is present and visible; if so, adds it to the pending array
|
||||
* (and if not, removes it).
|
||||
*/
|
||||
protected checkUIBlocked(): void {
|
||||
const blocked = document.querySelector<HTMLElement>('span.core-loading-spinner, ion-loading, .click-block-active');
|
||||
protected async checkUIBlocked(): Promise<void> {
|
||||
await CoreUtils.nextTick();
|
||||
const blocked = document.querySelector<HTMLElement>('div.core-loading-container, ion-loading, .click-block-active');
|
||||
|
||||
if (blocked?.offsetParent) {
|
||||
if (!this.waitingBlocked) {
|
||||
|
@ -216,23 +215,25 @@ export class TestsBehatBlockingService {
|
|||
let requestIndex = 0;
|
||||
|
||||
XMLHttpRequest.prototype.open = function(...args) {
|
||||
const index = requestIndex++;
|
||||
const key = 'httprequest-' + index;
|
||||
NgZone.run(() => {
|
||||
const index = requestIndex++;
|
||||
const key = 'httprequest-' + index;
|
||||
|
||||
try {
|
||||
try {
|
||||
// Add to the list of pending requests.
|
||||
TestsBehatBlocking.block(key);
|
||||
TestsBehatBlocking.block(key);
|
||||
|
||||
// Detect when it finishes and remove it from the list.
|
||||
this.addEventListener('loadend', () => {
|
||||
// Detect when it finishes and remove it from the list.
|
||||
this.addEventListener('loadend', () => {
|
||||
TestsBehatBlocking.unblock(key);
|
||||
});
|
||||
|
||||
return realOpen.apply(this, args);
|
||||
} catch (error) {
|
||||
TestsBehatBlocking.unblock(key);
|
||||
});
|
||||
|
||||
return realOpen.apply(this, args);
|
||||
} catch (error) {
|
||||
TestsBehatBlocking.unblock(key);
|
||||
throw error;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { NgZone } from '@singletons';
|
||||
import { TestsBehatBlocking } from './behat-blocking';
|
||||
import { TestBehatElementLocator } from './behat-runtime';
|
||||
|
||||
|
@ -409,30 +411,27 @@ export class TestsBehatDomUtils {
|
|||
* Make sure that an element is visible and wait to trigger the callback.
|
||||
*
|
||||
* @param element Element.
|
||||
* @param callback Callback called when the element is visible, passing bounding box parameter.
|
||||
*/
|
||||
protected static ensureElementVisible(element: HTMLElement, callback: (rect: DOMRect) => void): void {
|
||||
protected static async ensureElementVisible(element: HTMLElement): Promise<DOMRect> {
|
||||
const initialRect = element.getBoundingClientRect();
|
||||
|
||||
element.scrollIntoView(false);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
return new Promise<DOMRect>((resolve): void => {
|
||||
requestAnimationFrame(() => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
|
||||
if (initialRect.y !== rect.y) {
|
||||
setTimeout(() => {
|
||||
callback(rect);
|
||||
}, 300);
|
||||
if (initialRect.y !== rect.y) {
|
||||
setTimeout(() => {
|
||||
resolve(rect);
|
||||
}, 300);
|
||||
|
||||
TestsBehatBlocking.delay();
|
||||
return;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
callback(rect);
|
||||
resolve(rect);
|
||||
});
|
||||
});
|
||||
|
||||
TestsBehatBlocking.delay();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -440,16 +439,9 @@ export class TestsBehatDomUtils {
|
|||
*
|
||||
* @param element Element to press.
|
||||
*/
|
||||
static pressElement(element: HTMLElement): void {
|
||||
this.ensureElementVisible(element, (rect) => {
|
||||
// Simulate a mouse click on the button.
|
||||
const eventOptions = {
|
||||
clientX: rect.left + rect.width / 2,
|
||||
clientY: rect.top + rect.height / 2,
|
||||
bubbles: true,
|
||||
view: window,
|
||||
cancelable: true,
|
||||
};
|
||||
static async pressElement(element: HTMLElement): Promise<void> {
|
||||
NgZone.run(async () => {
|
||||
const blockKey = TestsBehatBlocking.block();
|
||||
|
||||
// Events don't bubble up across Shadow DOM boundaries, and some buttons
|
||||
// may not work without doing this.
|
||||
|
@ -459,6 +451,17 @@ export class TestsBehatDomUtils {
|
|||
element = parentElement;
|
||||
}
|
||||
|
||||
const rect = await this.ensureElementVisible(element);
|
||||
|
||||
// Simulate a mouse click on the button.
|
||||
const eventOptions: MouseEventInit = {
|
||||
clientX: rect.left + rect.width / 2,
|
||||
clientY: rect.top + rect.height / 2,
|
||||
bubbles: true,
|
||||
view: window,
|
||||
cancelable: true,
|
||||
};
|
||||
|
||||
// There are some buttons in the app that don't respond to click events, for example
|
||||
// buttons using the core-supress-events directive. That's why we need to send both
|
||||
// click and mouse events.
|
||||
|
@ -467,10 +470,65 @@ export class TestsBehatDomUtils {
|
|||
setTimeout(() => {
|
||||
element.dispatchEvent(new MouseEvent('mouseup', eventOptions));
|
||||
element.click();
|
||||
}, 300);
|
||||
|
||||
// Mark busy until the button click finishes processing.
|
||||
TestsBehatBlocking.delay();
|
||||
TestsBehatBlocking.unblock(blockKey);
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an element value.
|
||||
*
|
||||
* @param element HTML to set.
|
||||
* @param value Value to be set.
|
||||
*/
|
||||
static async setElementValue(element: HTMLElement, value: string): Promise<void> {
|
||||
NgZone.run(async () => {
|
||||
const blockKey = TestsBehatBlocking.block();
|
||||
|
||||
// Functions to get/set value depending on field type.
|
||||
let setValue = (text: string) => {
|
||||
element.innerHTML = text;
|
||||
};
|
||||
let getValue = () => element.innerHTML;
|
||||
|
||||
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
||||
setValue = (text: string) => {
|
||||
element.value = text;
|
||||
};
|
||||
getValue = () => element.value;
|
||||
}
|
||||
|
||||
// Pretend we have cut and pasted the new text.
|
||||
let event: InputEvent;
|
||||
if (getValue() !== '') {
|
||||
event = new InputEvent('input', {
|
||||
bubbles: true,
|
||||
view: window,
|
||||
cancelable: true,
|
||||
inputType: 'deleteByCut',
|
||||
});
|
||||
|
||||
await CoreUtils.nextTick();
|
||||
setValue('');
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
|
||||
if (value !== '') {
|
||||
event = new InputEvent('input', {
|
||||
bubbles: true,
|
||||
view: window,
|
||||
cancelable: true,
|
||||
inputType: 'insertFromPaste',
|
||||
data: value,
|
||||
});
|
||||
|
||||
await CoreUtils.nextTick();
|
||||
setValue(value);
|
||||
element.dispatchEvent(event);
|
||||
}
|
||||
|
||||
TestsBehatBlocking.unblock(blockKey);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -69,12 +69,16 @@ export class TestsBehatRuntime {
|
|||
* @return OK if successful, or ERROR: followed by message.
|
||||
*/
|
||||
static async handleCustomURL(url: string): Promise<string> {
|
||||
const blockKey = TestsBehatBlocking.block();
|
||||
|
||||
try {
|
||||
await CoreCustomURLSchemes.handleCustomURL(url);
|
||||
|
||||
return 'OK';
|
||||
} catch (error) {
|
||||
return 'ERROR: ' + error.message;
|
||||
} finally {
|
||||
TestsBehatBlocking.unblock(blockKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -330,49 +334,7 @@ export class TestsBehatRuntime {
|
|||
return 'ERROR: No element matches field to set.';
|
||||
}
|
||||
|
||||
// Functions to get/set value depending on field type.
|
||||
let setValue = (text: string) => {
|
||||
found.innerHTML = text;
|
||||
};
|
||||
let getValue = () => found.innerHTML;
|
||||
|
||||
if (found instanceof HTMLInputElement || found instanceof HTMLTextAreaElement) {
|
||||
setValue = (text: string) => {
|
||||
found.value = text;
|
||||
};
|
||||
getValue = () => found.value;
|
||||
}
|
||||
|
||||
// Pretend we have cut and pasted the new text.
|
||||
let event: InputEvent;
|
||||
if (getValue() !== '') {
|
||||
event = new InputEvent('input', {
|
||||
bubbles: true,
|
||||
view: window,
|
||||
cancelable: true,
|
||||
inputType: 'deleteByCut',
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
setValue('');
|
||||
found.dispatchEvent(event);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (value !== '') {
|
||||
event = new InputEvent('input', {
|
||||
bubbles: true,
|
||||
view: window,
|
||||
cancelable: true,
|
||||
inputType: 'insertFromPaste',
|
||||
data: value,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
setValue(value);
|
||||
found.dispatchEvent(event);
|
||||
}, 0);
|
||||
}
|
||||
TestsBehatDomUtils.setElementValue(found, value);
|
||||
|
||||
return 'OK';
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue