* Upgrade Nighwatch

* Add Microsoft header

* Fix yarn.lock
This commit is contained in:
Joel Mut 2024-10-30 10:48:20 -03:00 коммит произвёл GitHub
Родитель dccf5896c9
Коммит 2bdedaae10
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
14 изменённых файлов: 1049 добавлений и 725 удалений

Просмотреть файл

@ -2,15 +2,23 @@
"private": true,
"name": "botbuilder-js",
"version": "4.13.0",
"workspaces": [
"libraries/*",
"libraries/functional-tests/dialogToDialog/*",
"libraries/testskills/*",
"testing/*",
"testing/browser-functional/browser-echo-bot",
"tools",
"transcripts"
],
"workspaces": {
"packages": [
"libraries/*",
"libraries/functional-tests/dialogToDialog/*",
"libraries/testskills/*",
"testing/*",
"testing/browser-functional/browser-echo-bot",
"tools",
"transcripts"
],
"nohoist": [
"**/@types/selenium-webdriver"
],
"nohoistComments": {
"**/@types/selenium-webdriver": "This package is excluded from the root @types folder as it requires ES2015+, whereas some BotBuilder libraries support ES5+."
}
},
"scripts": {
"browser-functional-test": "yarn workspace browser-functional-tests test",
"build": "wsrun -e -m -t build",

Просмотреть файл

@ -36,13 +36,7 @@ steps:
inputs:
versionSpec: 20.x
- script: |
mkdir "testing/browser-functional/drivers"
cp "%CHROMEWEBDRIVER%/chromedriver.exe" "testing/browser-functional/drivers"
cp "%GECKOWEBDRIVER%/geckodriver.exe" "testing/browser-functional/drivers"
displayName: use browser drivers
- script: yarn --network-timeout 600000
- script: yarn install --network-timeout 600000
displayName: yarn install
- script: yarn build
@ -78,11 +72,10 @@ steps:
echo "# Deploy source code"
call az webapp deployment source config-zip --resource-group "$(TestResourceGroup)" --name "$(TestWebApp)" --src "$(System.DefaultWorkingDirectory)/testing/browser-functional/browser-echo-bot/browser-echo-bot.zip"
- script: yarn browser-functional-test chrome
displayName: run chrome tests
- script: yarn browser-functional-test firefox
displayName: run firefox tests
- script: yarn browser-functional-test
env:
TestURI: $(TESTURI)
displayName: Run tests
- task: AzureCLI@1
displayName: 'Delete Resource Group'
@ -90,5 +83,4 @@ steps:
azureSubscription: '$(AzureSubscription)'
scriptLocation: inlineScript
inlineScript: 'call az group delete -n $(TestResourceGroup) --yes'
condition: succeededOrFailed()

Просмотреть файл

@ -1,113 +0,0 @@
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const cp = require('child_process');
const driversDir = 'drivers';
const browsers = {
CHROME: 'chrome',
FIREFOX: 'firefox',
};
const drivers = {
[browsers.CHROME]: {
name: 'chromedriver.exe',
path: path.join(__dirname, driversDir, 'chromedriver.exe'),
port: 9515,
url: 'https://googlechromelabs.github.io/chrome-for-testing',
browser: {
name: browsers.CHROME,
url: 'https://www.google.com/chrome',
exists(driver) {
return browserExists(driver.port, 'cannot find Chrome binary');
},
},
},
[browsers.FIREFOX]: {
name: 'geckodriver.exe',
path: path.join(__dirname, driversDir, 'geckodriver.exe'),
port: 4444,
url: 'https://github.com/mozilla/geckodriver/releases',
browser: {
name: browsers.FIREFOX,
url: 'https://www.mozilla.org/firefox/new',
exists(driver) {
return browserExists(driver.port, 'unable to find binary');
},
},
},
};
async function requirements() {
const driver = drivers[process.argv[process.argv.indexOf('-e') + 1]?.trim().toLowerCase()];
if (!driver) {
return true;
}
console.log('[Validating Requirements]');
const exists = fs.existsSync(driver.path);
if (!exists) {
console.error(
` ❌ Driver : ${driver.name}
- The current Nightwatch configuration (nightwatch.conf.js) requires the '${driver.name}' to be installed inside the '${driversDir}' folder.
Visit '${driver.url}' website to download the '${driver.browser.name}' driver.
Make sure to take into account OS 'platform', 'architecture', and 'version'.`
);
return false;
}
console.log(` ✅ Driver : ${driver.name}`);
if(process.env.TF_BUILD != undefined) {
console.log(` ✅ Browser: ${driver.browser.name}`);
return true;
}
const exe = cp.spawn(driver.path, [`--port=${driver.port}`], {
encoding: 'utf-8',
shell: false,
detached: true,
windowsHide: true,
});
const isDriverRunning = await new Promise((res) => exe.stdout.on('data', () => res(true)));
const browserExists = isDriverRunning && (await driver.browser.exists(driver));
process.kill(exe.pid, 'SIGKILL');
if (browserExists) {
console.log(` ✅ Browser: ${driver.browser.name}`);
return true;
}
console.error(
` ❌ Browser: ${driver.browser.name}
- The current Nightwatch configuration (nightwatch.conf.js) requires the '${driver.browser.name}' browser to be installed.
Visit '${driver.browser.url}' website to download and install it.`
);
return false;
}
async function browserExists(port, assertion) {
try {
const { data } = await axios.post(`http://localhost:${port}/session`, { capabilities: {} });
await axios.delete(`http://localhost:${port}/session/${data.value.sessionId}`);
return true;
} catch (error) {
if (error?.response?.data?.value?.message?.includes(assertion)) {
return false;
}
throw error;
}
}
module.exports = {
drivers,
browsers,
async before() {
if (!(await requirements())) {
process.exit(1);
}
},
};

Просмотреть файл

@ -1,58 +0,0 @@
const { drivers, browsers } = require('./globals');
const seleniumServer = require('selenium-server');
module.exports = {
src_folders: ['tests'],
page_objects_path: 'tests/tests_pages',
globals_path: './globals.js',
test_settings: {
default: {
request_timeout_options: {
timeout: 100000,
retry_attempts: 3,
},
},
selenium: {
selenium: {
start_process: true,
check_process_delay: 10000,
port: drivers[browsers.FIREFOX].port,
server_path: seleniumServer.path,
cli_args: {
'webdriver.gecko.driver': drivers[browsers.FIREFOX].path,
},
},
webdriver: {
start_process: false,
},
},
[browsers.CHROME]: {
silent: true,
selenium: {
start_process: false,
},
webdriver: {
start_process: true,
server_path: drivers[browsers.CHROME].path,
port: drivers[browsers.CHROME].port,
},
desiredCapabilities: {
browserName: browsers.CHROME,
javascriptEnabled: true,
acceptSslCerts: true,
},
},
[browsers.FIREFOX]: {
extends: 'selenium',
silent: true,
desiredCapabilities: {
browserName: browsers.FIREFOX,
javascriptEnabled: true,
acceptSslCerts: true,
},
},
},
};

Просмотреть файл

@ -1 +0,0 @@
require('nightwatch/bin/runner.js');

Просмотреть файл

@ -0,0 +1,149 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
// Refer to the online docs for more details:
// https://nightwatchjs.org/gettingstarted/configuration/
//
// _ _ _ _ _ _ _
// | \ | |(_) | | | | | | | |
// | \| | _ __ _ | |__ | |_ __ __ __ _ | |_ ___ | |__
// | . ` || | / _` || '_ \ | __|\ \ /\ / / / _` || __| / __|| '_ \
// | |\ || || (_| || | | || |_ \ V V / | (_| || |_ | (__ | | | |
// \_| \_/|_| \__, ||_| |_| \__| \_/\_/ \__,_| \__| \___||_| |_|
// __/ |
// |___/
const dotenv = require('dotenv');
dotenv.config();
const { DEFAULT_BROWSER, browsers } = require('./utils');
const { validate } = require('./requirements');
const config = {
// An array of folders (excluding subfolders) where your tests are located;
// if this is not specified, the test source must be passed as the second argument to the test runner.
src_folders: ['nightwatch/tests'],
// See https://nightwatchjs.org/guide/concepts/page-object-model.html
page_objects_path: ['nightwatch/pages'],
// See https://nightwatchjs.org/guide/extending-nightwatch/adding-custom-commands.html
custom_commands_path: [],
// See https://nightwatchjs.org/guide/extending-nightwatch/adding-custom-assertions.html
custom_assertions_path: [],
// See https://nightwatchjs.org/guide/extending-nightwatch/adding-plugins.html
plugins: [],
// See https://nightwatchjs.org/guide/concepts/test-globals.html
// globals_path: './globals.js',
globals: {
asyncHookTimeout: 10 * 60 * 1000, // 10 minutes
async before() {
const valid = await validate();
if (!valid) {
process.exit(1);
}
},
},
webdriver: {},
test_workers: {
enabled: true,
},
test_settings: {
default: {
disable_error_log: false,
launch_url: process.env.TestURI,
screenshots: {
enabled: false,
path: 'screens',
on_failure: true,
},
desiredCapabilities: {
browserName: DEFAULT_BROWSER,
},
webdriver: {
start_process: true,
server_path: '',
},
},
[browsers.firefox.key]: {
desiredCapabilities: {
browserName: browsers.firefox.id,
alwaysMatch: {
acceptInsecureCerts: true,
'moz:firefoxOptions': {
args: [
// '-headless',
// '-verbose'
],
},
},
},
webdriver: {
start_process: true,
server_path: '',
cli_args: [
// very verbose geckodriver logs
// '-vv'
],
},
},
[browsers.chrome.key]: {
desiredCapabilities: {
browserName: browsers.chrome.id,
'goog:chromeOptions': {
// More info on Chromedriver: https://sites.google.com/a/chromium.org/chromedriver/
args: [
//'--no-sandbox',
//'--ignore-certificate-errors',
//'--allow-insecure-localhost',
//'--headless=new'
],
},
},
webdriver: {
start_process: true,
server_path: '',
cli_args: [
// --verbose
],
},
},
[browsers.edge.key]: {
desiredCapabilities: {
browserName: browsers.edge.id,
'ms:edgeOptions': {
// More info on EdgeDriver: https://docs.microsoft.com/en-us/microsoft-edge/webdriver-chromium/capabilities-edge-options
args: [
//'--headless=new'
],
},
},
webdriver: {
start_process: true,
server_path: '',
cli_args: [
// --verbose
],
},
},
},
};
module.exports = config;

Просмотреть файл

@ -3,25 +3,23 @@
* Licensed under the MIT License.
*/
const path = require('path');
const dotenv = require('dotenv');
const ENV_FILE = path.join(__dirname, '.env');
dotenv.config({ path: ENV_FILE });
module.exports = {
url: process.env.TestURI,
name: 'EchoBot',
url: function(){
return this.api.launchUrl;
},
elements: {
webchatContainer: {
selector: 'div[id=webchat]>div'
selector: 'div[id=webchat]>div',
},
webchatMessagesList: {
selector: 'section.webchat__basic-transcript__transcript'
selector: 'section.webchat__basic-transcript__transcript',
},
webchatMessageInput: {
selector: 'input[data-id=webchat-sendbox-input]'
selector: 'input[data-id=webchat-sendbox-input]',
},
webchatMessageInputSubmitButton: {
selector: 'button.webchat__send-button'
}
}
}
selector: 'button.webchat__send-button',
},
},
};

Просмотреть файл

@ -0,0 +1,43 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const { DEFAULT_BROWSER, getFlag, getBrowser, logs, isBrowserInstalled, isBotRunning } = require('./utils');
async function validate() {
const inputs = getFlag(['-e', '--env']).split(',');
let isBotValidated = false;
for (const input of inputs) {
let /** @type {import('./types').IBrowser} */ browser, /** @type {Error} */ err;
[browser, err] = getBrowser(input);
if (err) {
logs.browserNotFoundWarn(err);
[browser] = getBrowser(DEFAULT_BROWSER);
}
if(!isBotValidated){
err = await isBotRunning(browser);
isBotValidated = true;
if (err) {
logs.echoBotNotRunningError(err);
return false;
} else {
logs.echoBotRunningLog();
}
}
err = await isBrowserInstalled(browser);
if (err) {
logs.browserNotFoundError(browser);
return false;
} else {
logs.browserInstalledLog(browser);
}
}
return true;
}
module.exports = { validate };

Просмотреть файл

@ -0,0 +1,60 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
describe('Echo Bot: Message Handling', () => {
const botPage = browser.page.echo();
before(() => botPage.navigate());
after((browser) => browser.quit());
it('Echo bot webchat is loaded', () => {
// Check Webchat container is initialized and is present in the page
botPage.assert.elementPresent('@webchatContainer');
});
it('Echo bot webchat sends messages', async () => {
// Type 'Hello' in the webchat input box and send it to the bot
botPage.setValue('@webchatMessageInput', 'Hello');
botPage.click('@webchatMessageInputSubmitButton', (result) => {
// Assertion to check the button was clickable and got triggered
browser.assert.strictEqual(result.status, 0, 'Message input working');
});
botPage.pause(250);
await assertMessageIsPresentInPage(botPage, 'Hello', 'Webchat contains user message');
});
it('Echo bot webchat echoes messages', async () => {
await assertMessageIsPresentInPage(botPage, '1: You said “Hello”', 'Webchat contains bot reply');
});
});
async function assertMessageIsPresentInPage(pageInstance, textSearch, assertMessage) {
let messagesListPromises = [];
// Get messages list from webchat
await pageInstance.api
.elements('@webchatMessagesList', function (messagesWebElements) {
for (let index = 0; index < messagesWebElements.value.length; index++) {
const webElement = messagesWebElements.value[index];
messagesListPromises.push(function (resolve) {
// Workaround for different implementations of the WebDriver API
// This will be fixed when all browsers makes use of Selenium Server v4 which is compliant with the W3C WebDriver standards
var elementId = webElement.ELEMENT != undefined ? webElement.ELEMENT : webElement.values()[0];
pageInstance.api.elementIdText(elementId, function (elementText) {
resolve(elementText.value == textSearch);
});
});
}
})
.then(function () {
Promise.all(messagesListPromises).then(function (results) {
let messageExists = results.some(function (value) {
return value;
});
// Check if any of the existing messages was equal to the needle.
pageInstance.assert.strictEqual(messageExists, true, assertMessage);
});
});
}

19
testing/browser-functional/nightwatch/types.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,19 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
export interface IBrowser {
id: string;
key: string;
name: string;
url: string;
}
export interface IBrowserList {
chrome: IBrowser;
firefox: IBrowser;
edge: IBrowser;
}
export type BrowserKeys = keyof IBrowserList;

Просмотреть файл

@ -0,0 +1,135 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
const { Builder, Browser } = require('selenium-webdriver');
const echoPage = require('./pages/echo');
const DEFAULT_BROWSER = Browser.CHROME;
/**
@typedef IBrowser
@type {import('./types').IBrowser}
*/
/**
@typedef IBrowserList
@type {import('./types').IBrowserList}
*/
/**
@typedef BrowserKeys
@type {import('./types').BrowserKeys}
*/
/**
* @type {IBrowserList}
*/
const browsers = {
chrome: {
key: 'chrome',
id: Browser.CHROME,
name: 'Chrome',
url: 'https://www.google.com/chrome',
},
firefox: {
key: 'firefox',
id: Browser.FIREFOX,
name: 'Firefox',
url: 'https://www.mozilla.org/firefox/new',
},
edge: {
key: 'edge',
id: Browser.EDGE,
name: 'Edge',
url: 'https://www.microsoft.com/edge',
},
};
const logs = {
browserInstalledLog(browser) {
console.info(` ✅ Browser '${browser.name}' detected`);
},
browserNotFoundWarn(err) {
console.warn(` ⚠️ ${err.message} - Using default browser: ${DEFAULT_BROWSER}`);
},
browserNotFoundError(browser) {
console.error(
` ❌ Browser '${browser.name}' binary not found - Please visit the following URL to download and install the required browser: ${browser.url()}`
);
},
echoBotNotRunningError(err) {
console.error(`${err.message} - Please start the bot by executing 'yarn start:echo' and try again`);
},
echoBotRunningLog() {
console.info(` ✅ The '${echoPage.name}' is running at '${process.env.TestURI}'`);
},
};
/**
* @param {IBrowser} browser
* @returns {Promise<Error>}
*/
async function isBrowserInstalled(browser) {
if (!browser) {
return false;
}
let driver;
try {
driver = await new Builder().forBrowser(browser.id).build();
} catch (e) {
return e;
} finally {
await driver?.quit();
}
}
/**
* @param {IBrowser} browser
* @returns {Promise<Error>}
*/
async function isBotRunning(browser) {
let driver;
try {
driver = await new Builder().forBrowser(browser.id).build();
await driver.navigate().to(process.env.TestURI);
} catch (error) {
return new Error(`The '${echoPage.name}' bot is not running at '${process.env.TestURI}'`);
} finally {
await driver?.quit();
}
}
/**
* @param {BrowserKeys} key
* @returns {[IBrowser, Error]}
*/
function getBrowser(key) {
const browser = key ?? '';
const result = browsers[browser.trim().toLowerCase()];
if (!result) {
return ['', new Error(`Browser '${browser}' not found`)];
}
return [result, null];
}
/**
* @param {string[]} flags
* @returns {string}
*/
function getFlag(flags) {
return process.argv.find((_, i, arr) => flags.includes(arr[i - 1]?.trim()));
}
module.exports = {
DEFAULT_BROWSER,
browsers,
logs,
isBrowserInstalled,
isBotRunning,
getBrowser,
getFlag,
};

Просмотреть файл

@ -7,14 +7,14 @@
"devDependencies": {
"axios": "^1.7.7",
"dotenv": "^16.4.5",
"nightwatch": "^2.6.21",
"selenium-server": "^3.141.59"
"nightwatch": "^3.7.0"
},
"directories": {
"test": "tests"
},
"scripts": {
"test": "node nightwatch.js -e"
"test": "nightwatch --parallel --fail-fast --env firefox,chrome,edge --config nightwatch/config.js",
"start:echo": "cd browser-echo-bot && yarn start"
},
"author": "Microsoft Corp.",
"license": "MIT"

Просмотреть файл

@ -1,69 +0,0 @@
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
var botPage;
module.exports = {
botPage: {},
before: function(browser) {
botPage = browser.page.echoBotPage();
},
after: function(browser) {
// End current page session
botPage.end();
},
'Echo bot webchat is loaded': function(browser) {
// Navigate to the Echo Bot page
// This step is performed made in the first test since navigation can't be done in the 'before' hook
botPage.navigate();
// Check Webchat container is initialized and is present in the page
botPage
.assert.elementPresent('@webchatContainer');
},
'Echo bot webchat sends messages': async function(browser) {
// Type 'Hello' in the webchat input box and send it to the bot
botPage
.setValue('@webchatMessageInput', 'Hello');
botPage
.click('@webchatMessageInputSubmitButton', function (result) {
// Assertion to check the button was clickable and got triggered
this.assert.strictEqual(result.status, 0, 'Message input working');
});
botPage
.pause(250);
await assertMessageIsPresentInPage(botPage, 'Hello', 'Webchat contains user message');
},
'Echo bot webchat echoes messages': async function(browser) {
await assertMessageIsPresentInPage(botPage, '1: You said “Hello”', 'Webchat contains bot reply');
}
};
async function assertMessageIsPresentInPage(pageInstance, textSearch, assertMessage) {
let messagesListPromises = [];
// Get messages list from webchat
await pageInstance.api.elements('@webchatMessagesList', function(messagesWebElements) {
for (let index = 0; (index < messagesWebElements.value.length); index++) {
const webElement = messagesWebElements.value[index];
messagesListPromises.push((function(resolve) {
// Workaround for different implementations of the WebDriver API
// This will be fixed when all browsers makes use of Selenium Server v4 which is compliant with the W3C WebDriver standards
var elementId = webElement.ELEMENT != undefined ? webElement.ELEMENT : webElement.values()[0];
pageInstance.api.elementIdText(elementId, function(elementText) {
resolve(elementText.value == textSearch);
});
}));
}
}).then(function(){
Promise.all(messagesListPromises)
.then(function (results) {
let messageExists = results.some(function (value) {
return value;
});
// Check if any of the existing messages was equal to the needle.
pageInstance.assert.strictEqual(messageExists, true, assertMessage);
});
});
}

1053
yarn.lock

Разница между файлами не показана из-за своего большого размера Загрузить разницу