feat(tests): Adds avatar test. (#15382)

* feat(tests): Adds join options.

* fix(tests): Fix opening tests by default with tenant.

* fix(tests): Renames a method.

* fix(tests): Moves a method from filmstrip to participants pane.

* fix(tests): Adds ok button to base dialog.

* fix(tests): Adds missing checks for using iframe API.

* feat(tests): Prettify the result html on error.

* fix(tests): Fixes checking when not in room.

* fix(tests): Adds profile button to toolbar.

* fix(tests): Adds avatar test.

* fix(tests): Fix all execute methods and await.

* fix(tests): Fix avatar checks.
This commit is contained in:
Дамян Минков
2024-12-12 08:29:15 -06:00
committed by GitHub
parent 4e81d4461b
commit 2dc135b80f
16 changed files with 984 additions and 85 deletions

449
package-lock.json generated
View File

@@ -140,6 +140,7 @@
"@types/moment-duration-format": "2.2.6",
"@types/offscreencanvas": "2019.7.2",
"@types/pixelmatch": "5.2.5",
"@types/pretty": "2.0.3",
"@types/punycode": "2.1.0",
"@types/react": "17.0.14",
"@types/react-dom": "17.0.14",
@@ -176,6 +177,7 @@
"jsonwebtoken": "9.0.2",
"metro-react-native-babel-preset": "0.77.0",
"patch-package": "6.4.7",
"pretty": "2.0.0",
"process": "0.11.10",
"sass": "1.26.8",
"style-loader": "3.3.1",
@@ -4661,6 +4663,12 @@
"node": ">= 8"
}
},
"node_modules/@one-ini/wasm": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
"dev": true
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -7219,6 +7227,12 @@
"@types/node": "*"
}
},
"node_modules/@types/pretty": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/pretty/-/pretty-2.0.3.tgz",
"integrity": "sha512-xR96pShNlrxLd3gZqzCnbaAmbYhiRYjW51CDFjektZemqpBZBAAkMwxm4gBraJP/xSgKcsQhLXdlXOwDNWo4VQ==",
"dev": true
},
"node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -8659,6 +8673,15 @@
"integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==",
"deprecated": "Use your platform's native atob() and btoa() methods instead"
},
"node_modules/abbrev": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
"integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
"dev": true,
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -10624,6 +10647,42 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"node_modules/condense-newlines": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/condense-newlines/-/condense-newlines-0.2.1.tgz",
"integrity": "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==",
"dev": true,
"dependencies": {
"extend-shallow": "^2.0.1",
"is-whitespace": "^0.3.0",
"kind-of": "^3.0.2"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/condense-newlines/node_modules/kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
"dev": true,
"dependencies": {
"is-buffer": "^1.1.5"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/config-chain": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
"integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
"dev": true,
"dependencies": {
"ini": "^1.3.4",
"proto-list": "~1.2.1"
}
},
"node_modules/connect": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
@@ -11573,6 +11632,57 @@
"node": "^16.13.0 || >=18.0.0"
}
},
"node_modules/editorconfig": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
"integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
"dev": true,
"dependencies": {
"@one-ini/wasm": "0.1.1",
"commander": "^10.0.0",
"minimatch": "9.0.1",
"semver": "^7.5.3"
},
"bin": {
"editorconfig": "bin/editorconfig"
},
"engines": {
"node": ">=14"
}
},
"node_modules/editorconfig/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/editorconfig/node_modules/commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"dev": true,
"engines": {
"node": ">=14"
}
},
"node_modules/editorconfig/node_modules/minimatch": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
"integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -12922,6 +13032,18 @@
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"node_modules/extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
"dev": true,
"dependencies": {
"is-extendable": "^0.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -14451,6 +14573,12 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"dev": true
},
"node_modules/inline-style-prefixer": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-6.0.4.tgz",
@@ -14708,6 +14836,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -14987,6 +15124,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-whitespace": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/is-whitespace/-/is-whitespace-0.3.0.tgz",
"integrity": "sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@@ -15754,6 +15900,80 @@
"resolved": "https://registry.npmjs.org/jquery-i18next/-/jquery-i18next-1.2.1.tgz",
"integrity": "sha512-UNcw3rgxoKjGEg4w23FEn2h3OlPJU7rPzsgDuXDBZktIzeiVbJohs9Cv9hj8oP8KNfBRKOoErL/OVxg2FaAR4g=="
},
"node_modules/js-beautify": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz",
"integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==",
"dev": true,
"dependencies": {
"config-chain": "^1.1.13",
"editorconfig": "^1.0.4",
"glob": "^10.3.3",
"js-cookie": "^3.0.5",
"nopt": "^7.2.0"
},
"bin": {
"css-beautify": "js/bin/css-beautify.js",
"html-beautify": "js/bin/html-beautify.js",
"js-beautify": "js/bin/js-beautify.js"
},
"engines": {
"node": ">=14"
}
},
"node_modules/js-beautify/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/js-beautify/node_modules/glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/js-beautify/node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"dev": true,
"engines": {
"node": ">=14"
}
},
"node_modules/js-beautify/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/js-cookie": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz",
@@ -17945,6 +18165,21 @@
"url": "https://github.com/sponsors/antelle"
}
},
"node_modules/nopt": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
"integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
"dev": true,
"dependencies": {
"abbrev": "^2.0.0"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/normalize-package-data": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz",
@@ -18909,6 +19144,20 @@
"node": ">= 0.8.0"
}
},
"node_modules/pretty": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pretty/-/pretty-2.0.0.tgz",
"integrity": "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==",
"dev": true,
"dependencies": {
"condense-newlines": "^0.2.1",
"extend-shallow": "^2.0.1",
"js-beautify": "^1.6.12"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/pretty-format": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
@@ -19040,6 +19289,12 @@
"node": ">=0.10"
}
},
"node_modules/proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
"integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
"dev": true
},
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
@@ -27445,6 +27700,12 @@
"fastq": "^1.6.0"
}
},
"@one-ini/wasm": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
"dev": true
},
"@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -29247,6 +29508,12 @@
"@types/node": "*"
}
},
"@types/pretty": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/pretty/-/pretty-2.0.3.tgz",
"integrity": "sha512-xR96pShNlrxLd3gZqzCnbaAmbYhiRYjW51CDFjektZemqpBZBAAkMwxm4gBraJP/xSgKcsQhLXdlXOwDNWo4VQ==",
"dev": true
},
"@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -30332,6 +30599,12 @@
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
"integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA=="
},
"abbrev": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
"integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
"dev": true
},
"abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -31769,6 +32042,38 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"condense-newlines": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/condense-newlines/-/condense-newlines-0.2.1.tgz",
"integrity": "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-whitespace": "^0.3.0",
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"config-chain": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
"integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
"dev": true,
"requires": {
"ini": "^1.3.4",
"proto-list": "~1.2.1"
}
},
"connect": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
@@ -32449,6 +32754,44 @@
}
}
},
"editorconfig": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
"integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
"dev": true,
"requires": {
"@one-ini/wasm": "0.1.1",
"commander": "^10.0.0",
"minimatch": "9.0.1",
"semver": "^7.5.3"
},
"dependencies": {
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0"
}
},
"commander": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
"integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
"dev": true
},
"minimatch": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
"integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
"dev": true,
"requires": {
"brace-expansion": "^2.0.1"
}
}
}
},
"ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -33435,6 +33778,15 @@
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
},
"external-editor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
@@ -34547,6 +34899,12 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"dev": true
},
"inline-style-prefixer": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-6.0.4.tgz",
@@ -34733,6 +35091,12 @@
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="
},
"is-extendable": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
"integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
"dev": true
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -34897,6 +35261,12 @@
"call-bind": "^1.0.2"
}
},
"is-whitespace": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/is-whitespace/-/is-whitespace-0.3.0.tgz",
"integrity": "sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==",
"dev": true
},
"is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@@ -35454,6 +35824,59 @@
"resolved": "https://registry.npmjs.org/jquery-i18next/-/jquery-i18next-1.2.1.tgz",
"integrity": "sha512-UNcw3rgxoKjGEg4w23FEn2h3OlPJU7rPzsgDuXDBZktIzeiVbJohs9Cv9hj8oP8KNfBRKOoErL/OVxg2FaAR4g=="
},
"js-beautify": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz",
"integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==",
"dev": true,
"requires": {
"config-chain": "^1.1.13",
"editorconfig": "^1.0.4",
"glob": "^10.3.3",
"js-cookie": "^3.0.5",
"nopt": "^7.2.0"
},
"dependencies": {
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0"
}
},
"glob": {
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"requires": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
}
},
"js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"dev": true
},
"minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"requires": {
"brace-expansion": "^2.0.1"
}
}
}
},
"js-cookie": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz",
@@ -37117,6 +37540,15 @@
"resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz",
"integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw=="
},
"nopt": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
"integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
"dev": true,
"requires": {
"abbrev": "^2.0.0"
}
},
"normalize-package-data": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz",
@@ -37784,6 +38216,17 @@
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true
},
"pretty": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/pretty/-/pretty-2.0.0.tgz",
"integrity": "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==",
"dev": true,
"requires": {
"condense-newlines": "^0.2.1",
"extend-shallow": "^2.0.1",
"js-beautify": "^1.6.12"
}
},
"pretty-format": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
@@ -37885,6 +38328,12 @@
"integrity": "sha512-qYNxyMj1JeW54i/EWEFsM1cVwxJbtgPp8+0Wg9XjNaK6VE/c4oRi6PNu5p7w1mNXEIQIjV5Wwn8v8Gz82/QzdQ==",
"dev": true
},
"proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
"integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
"dev": true
},
"proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",

View File

@@ -146,6 +146,7 @@
"@types/moment-duration-format": "2.2.6",
"@types/offscreencanvas": "2019.7.2",
"@types/pixelmatch": "5.2.5",
"@types/pretty": "2.0.3",
"@types/punycode": "2.1.0",
"@types/react": "17.0.14",
"@types/react-dom": "17.0.14",
@@ -182,6 +183,7 @@
"jsonwebtoken": "9.0.2",
"metro-react-native-babel-preset": "0.77.0",
"patch-package": "6.4.7",
"pretty": "2.0.0",
"process": "0.11.10",
"sass": "1.26.8",
"style-loader": "3.3.1",

View File

@@ -106,7 +106,8 @@ const StageParticipantNameLabel = () => {
classes.badgeContainer,
toolboxVisible && classes.containerElevated,
_isScreenShareParticipant && classes.screenSharing
) }>
) }
data-testid = 'stage-display-name' >
<DisplayNameBadge name = { nameToDisplay } />
</div>
);

View File

@@ -7,11 +7,12 @@ import { urlObjectToString } from '../../react/features/base/util/uri';
import Filmstrip from '../pageobjects/Filmstrip';
import IframeAPI from '../pageobjects/IframeAPI';
import ParticipantsPane from '../pageobjects/ParticipantsPane';
import SettingsDialog from '../pageobjects/SettingsDialog';
import Toolbar from '../pageobjects/Toolbar';
import VideoQualityDialog from '../pageobjects/VideoQualityDialog';
import { LOG_PREFIX, logInfo } from './browserLogger';
import { IContext } from './types';
import { IContext, IJoinOptions } from './types';
/**
* Participant.
@@ -112,21 +113,25 @@ export class Participant {
* Joins conference.
*
* @param {IContext} context - The context.
* @param {boolean} skipInMeetingChecks - Whether to skip in meeting checks.
* @param {IJoinOptions} options - Options for joining.
* @returns {Promise<void>}
*/
async joinConference(context: IContext, skipInMeetingChecks = false): Promise<void> {
async joinConference(context: IContext, options: IJoinOptions = {}): Promise<void> {
const config = {
room: context.roomName,
configOverwrite: this.config,
interfaceConfigOverwrite: {
SHOW_CHROME_EXTENSION_BANNER: false
},
userInfo: {
displayName: this._name
}
};
if (!options.skipDisplayName) {
// @ts-ignore
config.userInfo = {
displayName: this._name
};
}
if (context.iframeAPI) {
config.room = 'iframeAPITest.html';
}
@@ -168,7 +173,7 @@ export class Participant {
await this.waitToJoinMUC();
await this.postLoadProcess(skipInMeetingChecks);
await this.postLoadProcess(options.skipInMeetingChecks);
}
/**
@@ -178,7 +183,7 @@ export class Participant {
* @returns {Promise<void>}
* @private
*/
private async postLoadProcess(skipInMeetingChecks: boolean): Promise<void> {
private async postLoadProcess(skipInMeetingChecks = false): Promise<void> {
const driver = this.driver;
const parallel = [];
@@ -227,7 +232,7 @@ export class Participant {
*/
async waitForPageToLoad(): Promise<void> {
return this.driver.waitUntil(
() => this.driver.execute(() => document.readyState === 'complete'),
async () => await this.driver.execute(() => document.readyState === 'complete'),
{
timeout: 30_000, // 30 seconds
timeoutMsg: 'Timeout waiting for Page Load Request to complete.'
@@ -238,8 +243,8 @@ export class Participant {
/**
* Checks if the participant is in the meeting.
*/
isInMuc() {
return this.driver.execute(() => APP.conference.isJoined());
async isInMuc() {
return await this.driver.execute(() => typeof APP !== 'undefined' && APP.conference?.isJoined());
}
/**
@@ -266,7 +271,7 @@ export class Participant {
const driver = this.driver;
return driver.waitUntil(async () =>
driver.execute(() => APP.conference.getConnectionState() === 'connected'), {
await driver.execute(() => APP.conference.getConnectionState() === 'connected'), {
timeout: 15_000,
timeoutMsg: 'expected ICE to be connected for 15s'
});
@@ -281,7 +286,7 @@ export class Participant {
const driver = this.driver;
return driver.waitUntil(async () =>
driver.execute(() => {
await driver.execute(() => {
const stats = APP.conference.getStats();
const bitrateMap = stats?.bitrate || {};
const rtpStats = {
@@ -306,7 +311,7 @@ export class Participant {
const driver = this.driver;
return driver.waitUntil(async () =>
driver.execute(count => APP.conference.getNumberOfParticipantsWithTracks() >= count, number), {
await driver.execute(count => APP.conference.getNumberOfParticipantsWithTracks() >= count, number), {
timeout: 15_000,
timeoutMsg: 'expected remote streams in 15s'
});
@@ -348,6 +353,15 @@ export class Participant {
return new VideoQualityDialog(this);
}
/**
* Returns the settings Dialog.
*
* @returns {SettingsDialog}
*/
getSettingsDialog(): SettingsDialog {
return new SettingsDialog(this);
}
/**
* Switches to the iframe API context
*/
@@ -371,6 +385,13 @@ export class Participant {
return new IframeAPI(this);
}
/**
* Hangups the participant by leaving the page. base.html is an empty page on all deployments.
*/
async hangup() {
await this.driver.url('/base.html');
}
/**
* Returns the local display name.
*/
@@ -383,4 +404,92 @@ export class Participant {
return await localDisplayName.getText();
}
/**
* Gets avatar SRC attribute for the one displayed on local video thumbnail.
*/
async getLocalVideoAvatar() {
const avatar
= this.driver.$('//span[@id="localVideoContainer"]//img[contains(@class,"userAvatar")]');
return await avatar.isExisting() ? await avatar.getAttribute('src') : null;
}
/**
* Gets avatar SRC attribute for the one displayed on large video.
*/
async getLargeVideoAvatar() {
const avatar = this.driver.$('//img[@id="dominantSpeakerAvatar"]');
return await avatar.isExisting() ? await avatar.getAttribute('src') : null;
}
/**
* Returns resource part of the JID of the user who is currently displayed in the large video area.
*/
async getLargeVideoResource() {
return await this.driver.execute(() => APP.UI.getLargeVideoID());
}
/**
* Makes sure that the avatar is displayed in the local thumbnail and that the video is not displayed.
* There are 3 options for avatar:
* - defaultAvatar: true - the default avatar (with grey figure) is used
* - image: true - the avatar is an image set in the settings
* - defaultAvatar: false, image: false - the avatar is produced from the initials of the display name
*/
async assertThumbnailShowsAvatar(
participant: Participant, reverse = false, defaultAvatar = false, image = false): Promise<void> {
const id = participant === this
? 'localVideoContainer' : `participant_${await participant.getEndpointId()}`;
const xpath = defaultAvatar
? `//span[@id='${id}']//div[contains(@class,'userAvatar') and contains(@class, 'defaultAvatar')]`
: `//span[@id="${id}"]//${image ? 'img' : 'div'}[contains(@class,"userAvatar")]`;
await this.driver.$(xpath).waitForDisplayed({
reverse,
timeout: 2000,
timeoutMsg: `Avatar is ${reverse ? '' : 'not'} displayed in the local thumbnail for ${participant.name}`
});
await this.driver.$(`//span[@id="${id}"]//video`).waitForDisplayed({
reverse: !reverse,
timeout: 2000,
timeoutMsg: `Video is ${reverse ? 'not' : ''} displayed in the local thumbnail for ${participant.name}`
});
}
/**
* Makes sure that the default avatar is used.
*/
async assertDefaultAvatarExist(participant: Participant): Promise<void> {
const id = participant === this
? 'localVideoContainer' : `participant_${await participant.getEndpointId()}`;
await this.driver.$(
`//span[@id='${id}']//div[contains(@class,'userAvatar') and contains(@class, 'defaultAvatar')]`)
.waitForExist({
timeout: 2000,
timeoutMsg: `Default avatar does not exist for ${participant.name}`
});
}
/**
* Makes sure that the local video is displayed in the local thumbnail and that the avatar is not displayed.
*/
async asserLocalThumbnailShowsVideo(): Promise<void> {
await this.assertThumbnailShowsAvatar(this, true);
}
/**
* Make sure a display name is visible on the stage.
* @param value
*/
async assertDisplayNameVisibleOnStage(value: string) {
const displayNameEl = this.driver.$('div[data-testid="stage-display-name"]');
expect(await displayNameEl.isDisplayed()).toBeTrue();
expect(await displayNameEl.getText()).toBe(value);
}
}

View File

@@ -5,7 +5,7 @@ import { v4 as uuidv4 } from 'uuid';
import { Participant } from './Participant';
import WebhookProxy from './WebhookProxy';
import { IContext } from './types';
import { IContext, IJoinOptions } from './types';
/**
* Generate a random room name.
@@ -31,16 +31,20 @@ function generateRandomRoomName(): string {
* Ensure that there is on participant.
*
* @param {IContext} context - The context.
* @param {IJoinOptions} options - The options to use when joining the participant.
* @returns {Promise<void>}
*/
export async function ensureOneParticipant(context: IContext): Promise<void> {
export async function ensureOneParticipant(context: IContext, options?: IJoinOptions): Promise<void> {
if (!context.roomName) {
context.roomName = generateRandomRoomName();
}
context.p1 = new Participant('participant1');
await context.p1.joinConference(context, true);
await context.p1.joinConference(context, {
...options,
skipInMeetingChecks: true
});
}
/**
@@ -80,9 +84,10 @@ export async function ensureThreeParticipants(context: IContext): Promise<void>
* Ensure that there are two participants.
*
* @param {Object} context - The context.
* @param {IJoinOptions} options - The options to join.
* @returns {Promise<void>}
*/
export async function ensureTwoParticipants(context: IContext): Promise<void> {
export async function ensureTwoParticipants(context: IContext, options?: IJoinOptions): Promise<void> {
if (!context.roomName) {
context.roomName = generateRandomRoomName();
}
@@ -98,12 +103,15 @@ export async function ensureTwoParticipants(context: IContext): Promise<void> {
// make sure the first participant is moderator, if supported by deployment
await _joinParticipant(p1DisplayName, context.p1, p => {
context.p1 = p;
}, true, token);
}, {
...options,
skipInMeetingChecks: true
}, token);
await Promise.all([
_joinParticipant('participant2', context.p2, p => {
context.p2 = p;
}),
}, options),
context.p1.waitForRemoteStreams(1),
context.p2.waitForRemoteStreams(1)
]);
@@ -114,24 +122,28 @@ export async function ensureTwoParticipants(context: IContext): Promise<void> {
* @param name - The name of the participant.
* @param p - The participant instance to prepare or undefined if new one is needed.
* @param setter - The setter to use for setting the new participant instance into the context if needed.
* @param {boolean} skipInMeetingChecks - Whether to skip in meeting checks.
* @param {boolean} options - Join options.
* @param {string?} jwtToken - The token to use if any.
*/
async function _joinParticipant( // eslint-disable-line max-params
name: string,
p: Participant,
setter: (p: Participant) => void,
skipInMeetingChecks = false,
options: IJoinOptions = {},
jwtToken?: string) {
if (p) {
await p.switchInPage();
if (context.iframeAPI) {
await p.switchInPage();
}
if (await p.isInMuc()) {
return;
}
// when loading url make sure we are on the top page context or strange errors may occur
await p.switchToAPI();
if (context.iframeAPI) {
// when loading url make sure we are on the top page context or strange errors may occur
await p.switchToAPI();
}
// Change the page so we can reload same url if we need to, base.html is supposed to be empty or close to empty
await p.driver.url('/base.html');
@@ -144,7 +156,7 @@ async function _joinParticipant( // eslint-disable-line max-params
// set the new participant instance, pass it to setter
setter(newParticipant);
return newParticipant.joinConference(context, skipInMeetingChecks);
await newParticipant.joinConference(context, options);
}
/**
@@ -157,13 +169,25 @@ async function _joinParticipant( // eslint-disable-line max-params
* the mute state of {@code testee}.
* @returns {Promise<void>}
*/
export async function toggleMuteAndCheck(testee: Participant, observer: Participant): Promise<void> {
export async function muteAudioAndCheck(testee: Participant, observer: Participant): Promise<void> {
await testee.getToolbar().clickAudioMuteButton();
await observer.getFilmstrip().assertAudioMuteIconIsDisplayed(testee);
await testee.getFilmstrip().assertAudioMuteIconIsDisplayed(testee);
}
/**
* Starts the video on testee and check on observer.
* @param testee
* @param observer
*/
export async function unMuteVideoAndCheck(testee: Participant, observer: Participant): Promise<void> {
await testee.getToolbar().clickVideoUnmuteButton();
await observer.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee, true);
await testee.getParticipantsPane().assertVideoMuteIconIsDisplayed(testee, true);
}
/**
* Get a JWT token for a moderator.
*/

View File

@@ -13,3 +13,16 @@ export type IContext = {
roomName: string;
webhooksProxy: WebhookProxy;
};
export type IJoinOptions = {
/**
* Whether to skip setting display name.
*/
skipDisplayName?: boolean;
/**
* Whether to skip in meeting checks like ice connected and send receive data. For single in meeting participant.
*/
skipInMeetingChecks?: boolean;
};

View File

@@ -1,6 +1,7 @@
import { Participant } from '../helpers/Participant';
const CLOSE_BUTTON = 'modal-header-close-button';
const OK_BUTTON = 'modal-dialog-ok-button';
/**
* Base class for all dialogs.
@@ -23,4 +24,11 @@ export default class BaseDialog {
async clickCloseButton(): Promise<void> {
await this.participant.driver.$(`#${CLOSE_BUTTON}`).click();
}
/**
* Clicks on the ok button.
*/
async clickOkButton(): Promise<void> {
await this.participant.driver.$(`#${OK_BUTTON}`).click();
}
}

View File

@@ -44,37 +44,6 @@ export default class Filmstrip {
});
}
/**
* Asserts that {@code participant} shows or doesn't show the video mute icon for the conference participant
* identified by {@code testee}.
*
* @param {Participant} testee - The {@code Participant} for whom we're checking the status of audio muted icon.
* @param {boolean} reverse - If {@code true}, the method will assert the absence of the "mute" icon;
* otherwise, it will assert its presence.
* @returns {Promise<void>}
*/
async assertVideoMuteIconIsDisplayed(testee: Participant, reverse = false): Promise<void> {
const isOpen = await this.participant.getParticipantsPane().isOpen();
if (!isOpen) {
await this.participant.getParticipantsPane().open();
}
const id = `participant-item-${await testee.getEndpointId()}`;
const mutedIconXPath
= `//div[@id='${id}']//div[contains(@class, 'indicators')]//*[local-name()='svg' and @id='videoMuted']`;
await this.participant.driver.$(mutedIconXPath).waitForDisplayed({
reverse,
timeout: 2000,
timeoutMsg: `Video mute icon is ${reverse ? '' : 'not'} displayed for ${testee.name}`
});
if (!isOpen) {
await this.participant.getParticipantsPane().close();
}
}
/**
* Returns the remote display name for an endpoint.
* @param endpointId The endpoint id.
@@ -86,4 +55,26 @@ export default class Filmstrip {
return await remoteDisplayName.getText();
}
/**
* Pins a participant by clicking on their thumbnail.
* @param participant The participant.
*/
async pinParticipant(participant: Participant) {
const id = participant === this.participant
? 'localVideoContainer' : `participant_${await participant.getEndpointId()}`;
await this.participant.driver.$(`//span[@id="${id}"]`).click();
}
/**
* Gets avatar SRC attribute for the one displayed on small video thumbnail.
* @param endpointId
*/
async getAvatar(endpointId: string) {
const elem = this.participant.driver.$(
`//span[@id='participant_${endpointId}']//img[contains(@class,'userAvatar')]`);
return await elem.isExisting() ? elem.getAttribute('src') : null;
}
}

View File

@@ -20,7 +20,7 @@ export default class IframeAPI {
* @param event
*/
async getEventResult(event: string): Promise<any> {
return this.participant.driver.execute(
return await this.participant.driver.execute(
eventName => {
const result = window.jitsiAPI.test[eventName];
@@ -37,28 +37,29 @@ export default class IframeAPI {
* @param eventName The event name.
*/
async addEventListener(eventName: string) {
return this.participant.driver.executeAsync((event, prefix, done) => {
console.log(`${new Date().toISOString()} ${prefix} Adding listener for event: ${event}`);
window.jitsiAPI.addListener(event, evt => {
console.log(`${new Date().toISOString()} ${prefix} Received ${event} event: ${JSON.stringify(evt)}`);
window.jitsiAPI.test[event] = evt;
});
done();
}, eventName, LOG_PREFIX);
return await this.participant.driver.execute(
(event, prefix) => {
console.log(`${new Date().toISOString()} ${prefix} Adding listener for event: ${event}`);
window.jitsiAPI.addListener(event, evt => {
console.log(
`${new Date().toISOString()} ${prefix} Received ${event} event: ${JSON.stringify(evt)}`);
window.jitsiAPI.test[event] = evt;
});
}, eventName, LOG_PREFIX);
}
/**
* Returns an array of available rooms and details of it.
*/
async getRoomsInfo() {
return this.participant.driver.execute(() => window.jitsiAPI.getRoomsInfo());
return await this.participant.driver.execute(() => window.jitsiAPI.getRoomsInfo());
}
/**
* Returns the number of participants in the conference.
*/
async getNumberOfParticipants() {
return this.participant.driver.execute(() => window.jitsiAPI.getNumberOfParticipants());
return await this.participant.driver.execute(() => window.jitsiAPI.getNumberOfParticipants());
}
/**
@@ -67,7 +68,7 @@ export default class IframeAPI {
* @param args The arguments.
*/
async executeCommand(command: string, ...args: any[]) {
return this.participant.driver.execute(
return await this.participant.driver.execute(
(commandName, commandArgs) =>
window.jitsiAPI.executeCommand(commandName, ...commandArgs)
, command, args);
@@ -77,14 +78,14 @@ export default class IframeAPI {
* Returns the current state of the participant's pane.
*/
async isParticipantsPaneOpen() {
return this.participant.driver.execute(() => window.jitsiAPI.isParticipantsPaneOpen());
return await this.participant.driver.execute(() => window.jitsiAPI.isParticipantsPaneOpen());
}
/**
* Removes the embedded Jitsi Meet conference.
*/
async dispose() {
return this.participant.driver.execute(() => window.jitsiAPI.dispose());
return await this.participant.driver.execute(() => window.jitsiAPI.dispose());
}
}

View File

@@ -44,4 +44,35 @@ export default class ParticipantsPane {
await this.participant.driver.$(`.${PARTICIPANTS_PANE}`).waitForDisplayed({ reverse: true });
}
/**
* Asserts that {@code participant} shows or doesn't show the video mute icon for the conference participant
* identified by {@code testee}.
*
* @param {Participant} testee - The {@code Participant} for whom we're checking the status of audio muted icon.
* @param {boolean} reverse - If {@code true}, the method will assert the absence of the "mute" icon;
* otherwise, it will assert its presence.
* @returns {Promise<void>}
*/
async assertVideoMuteIconIsDisplayed(testee: Participant, reverse = false): Promise<void> {
const isOpen = await this.isOpen();
if (!isOpen) {
await this.open();
}
const id = `participant-item-${await testee.getEndpointId()}`;
const mutedIconXPath
= `//div[@id='${id}']//div[contains(@class, 'indicators')]//*[local-name()='svg' and @id='videoMuted']`;
await this.participant.driver.$(mutedIconXPath).waitForDisplayed({
reverse,
timeout: 2000,
timeoutMsg: `Video mute icon is ${reverse ? '' : 'not'} displayed for ${testee.name}`
});
if (!isOpen) {
await this.close();
}
}
}

View File

@@ -0,0 +1,62 @@
import BaseDialog from './BaseDialog';
const EMAIL_FIELD = '#setEmail';
const SETTINGS_DIALOG_CONTENT = '.settings-pane';
const X_PATH_PROFILE_TAB = '//div[contains(@class, "settings-dialog")]//*[text()="Profile"]';
/**
* The settings dialog.
*/
export default class SettingsDialog extends BaseDialog {
/**
* Waits for the settings dialog to be visible.
*/
async waitForDisplay() {
await this.participant.driver.$(SETTINGS_DIALOG_CONTENT).waitForDisplayed();
}
/**
* Displays a specific tab in the settings dialog.
* @param xpath
* @private
*/
private async openTab(xpath: string) {
const elem = this.participant.driver.$(xpath);
await elem.waitForClickable();
await elem.click();
}
/**
* Selects the Profile tab to be displayed.
*/
async openProfileTab() {
await this.openTab(X_PATH_PROFILE_TAB);
}
/**
* Enters the passed in email into the email field.
* @param email
*/
async setEmail(email: string) {
await this.openProfileTab();
await this.participant.driver.$(EMAIL_FIELD).setValue(email);
}
/**
* Returns the participant's email displayed in the settings dialog.
*/
async getEmail() {
await this.openProfileTab();
return await this.participant.driver.$(EMAIL_FIELD).getValue();
}
/**
* Clicks the OK button on the settings dialog to close the dialog and save any changes made.
*/
async submit() {
await this.clickOkButton();
}
}

View File

@@ -7,6 +7,7 @@ const CLOSE_PARTICIPANTS_PANE = 'Close participants pane';
const OVERFLOW_MENU = 'More actions menu';
const OVERFLOW = 'More actions';
const PARTICIPANTS = 'Open participants pane';
const PROFILE = 'Edit your profile';
const VIDEO_QUALITY = 'Manage video quality';
const VIDEO_MUTE = 'Stop camera';
const VIDEO_UNMUTE = 'Start camera';
@@ -134,6 +135,13 @@ export default class Toolbar {
return this.clickButtonInOverflowMenu(VIDEO_QUALITY);
}
/**
* Clicks on the profile toolbar button which opens or closes the profile panel.
*/
async clickProfileButton(): Promise<void> {
return this.clickButtonInOverflowMenu(PROFILE);
}
/**
* Ensure the overflow menu is open and clicks on a specified button.
* @param accessibilityLabel The accessibility label of the button to be clicked.
@@ -203,4 +211,15 @@ export default class Toolbar {
timeoutMsg: `Overflow menu is not ${visible ? 'visible' : 'hidden'}`
});
}
/**
* Gets the participant's avatar image element located in the toolbar.
*/
async getProfileImage() {
await this.openOverflowMenu();
const elem = this.participant.driver.$(`[aria-label^="${PROFILE}"] img`);
return await elem.isExisting() ? await elem.getAttribute('src') : null;
}
}

View File

@@ -19,9 +19,7 @@ describe('Audio only - ', () => {
await context.p1.driver.$('//div[@id="dominantSpeaker"]').waitForDisplayed();
// Makes sure that the avatar is displayed in the local thumbnail and that the video is not displayed.
await context.p1.driver.$('//span[@id="localVideoContainer"]//div[contains(@class,"userAvatar")]')
.waitForDisplayed();
await context.p1.driver.$('//span[@id="localVideoWrapper"]//video').waitForDisplayed({ reverse: true });
await context.p1.assertThumbnailShowsAvatar(context.p1);
});
/**
@@ -51,10 +49,10 @@ describe('Audio only - ', () => {
*/
async function verifyVideoMute(muted: boolean) {
// Verify the observer sees the testee in the desired muted state.
await context.p2.getFilmstrip().assertVideoMuteIconIsDisplayed(context.p1, !muted);
await context.p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(context.p1, !muted);
// Verify the testee sees itself in the desired muted state.
await context.p1.getFilmstrip().assertVideoMuteIconIsDisplayed(context.p1, !muted);
await context.p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(context.p1, !muted);
}
/**

View File

@@ -1,14 +1,14 @@
/* global APP */
import type { Participant } from '../../helpers/Participant';
import { ensureThreeParticipants, toggleMuteAndCheck } from '../../helpers/participants';
import { ensureThreeParticipants, muteAudioAndCheck } from '../../helpers/participants';
describe('ActiveSpeaker ', () => {
it('testActiveSpeaker', async () => {
await ensureThreeParticipants(context);
await toggleMuteAndCheck(context.p1, context.p2);
await toggleMuteAndCheck(context.p2, context.p1);
await toggleMuteAndCheck(context.p3, context.p1);
await muteAudioAndCheck(context.p1, context.p2);
await muteAudioAndCheck(context.p2, context.p1);
await muteAudioAndCheck(context.p3, context.p1);
// participant1 becomes active speaker - check from participant2's perspective
await testActiveSpeaker(context.p1, context.p2, context.p3);
@@ -60,7 +60,8 @@ async function testActiveSpeaker(
const otherParticipant1Driver = otherParticipant1.driver;
await otherParticipant1Driver.waitUntil(
() => otherParticipant1Driver.execute((id: string) => APP.UI.getLargeVideoID() === id, speakerEndpoint),
async () => await otherParticipant1Driver.execute(
id => APP.UI.getLargeVideoID() === id, speakerEndpoint),
{
timeout: 30_000, // 30 seconds
timeoutMsg: 'Active speaker not displayed on large video.'

View File

@@ -0,0 +1,189 @@
import {
ensureThreeParticipants,
ensureTwoParticipants,
unMuteVideoAndCheck
} from '../../helpers/participants';
const EMAIL = 'support@jitsi.org';
const HASH = '38f014e4b7dde0f64f8157d26a8c812e';
describe('Avatar - ', () => {
it('setup the meeting', async () => {
// Start p1
await ensureTwoParticipants(context, {
skipDisplayName: true
});
});
it('change and check', async () => {
// check default avatar for p1 on p2
await context.p2.assertDefaultAvatarExist(context.p1);
await context.p1.getToolbar().clickProfileButton();
const settings = context.p1.getSettingsDialog();
await settings.waitForDisplay();
await settings.setEmail(EMAIL);
await settings.submit();
// check if the local avatar in the toolbar menu has changed
await context.p1.driver.waitUntil(
async () => (await context.p1.getToolbar().getProfileImage())?.includes(HASH), {
timeout: 3000, // give more time for the initial download of the image
timeoutMsg: 'Avatar has not changed for p1'
});
// check if the avatar in the local thumbnail has changed
expect(await context.p1.getLocalVideoAvatar()).toContain(HASH);
const p1EndpointId = await context.p1.getEndpointId();
await context.p2.driver.waitUntil(
async () => (await context.p2.getFilmstrip().getAvatar(p1EndpointId))?.includes(HASH), {
timeout: 5000,
timeoutMsg: 'Avatar has not changed for p1 on p2'
});
// check if the avatar in the large video has changed
expect(await context.p2.getLargeVideoAvatar()).toContain(HASH);
// we check whether the default avatar of participant2 is displayed on both sides
await context.p1.assertDefaultAvatarExist(context.p2);
await context.p2.assertDefaultAvatarExist(context.p2);
// the problem on FF where we can send keys to the input field,
// and the m from the text can mute the call, check whether we are muted
await context.p2.getFilmstrip().assertAudioMuteIconIsDisplayed(context.p1, true);
});
it('when video muted', async () => {
await context.p2.hangup();
// Mute p1's video
await context.p1.getToolbar().clickVideoMuteButton();
await context.p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(context.p1);
await context.p1.driver.waitUntil(
async () => (await context.p1.getLargeVideoAvatar())?.includes(HASH), {
timeout: 2000,
timeoutMsg: 'Avatar on large video did not change'
});
const p1LargeSrc = await context.p1.getLargeVideoAvatar();
const p1ThumbSrc = await context.p1.getLocalVideoAvatar();
// Check if avatar on large video is the same as on local thumbnail
expect(p1ThumbSrc).toBe(p1LargeSrc);
// Join p2
await ensureTwoParticipants(context, {
skipDisplayName: true
});
// Verify that p1 is muted from the perspective of p2
await context.p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(context.p1);
await context.p2.getFilmstrip().pinParticipant(context.p1);
// Check if p1's avatar is on large video now
await context.p2.driver.waitUntil(
async () => await context.p2.getLargeVideoAvatar() === p1LargeSrc, {
timeout: 2000,
timeoutMsg: 'Avatar on large video did not change'
});
// p1 pins p2's video
await context.p1.getFilmstrip().pinParticipant(context.p2);
// Check if avatar is displayed on p1's local video thumbnail
await context.p1.assertThumbnailShowsAvatar(context.p1, false, false, true);
// Unmute - now local avatar should be hidden and local video displayed
await unMuteVideoAndCheck(context.p1, context.p2);
await context.p1.asserLocalThumbnailShowsVideo();
// Now both p1 and p2 have video muted
await context.p1.getToolbar().clickVideoMuteButton();
await context.p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(context.p1);
await context.p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(context.p1);
await context.p2.getToolbar().clickVideoMuteButton();
await context.p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(context.p2);
await context.p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(context.p2);
// Start the third participant
await ensureThreeParticipants(context);
// Pin local video and verify avatars are displayed
await context.p3.getFilmstrip().pinParticipant(context.p3);
await context.p3.assertThumbnailShowsAvatar(context.p1, false, false, true);
await context.p3.assertThumbnailShowsAvatar(context.p2, false, true);
const p1EndpointId = await context.p1.getEndpointId();
const p2EndpointId = await context.p2.getEndpointId();
expect(await context.p3.getFilmstrip().getAvatar(p1EndpointId)).toBe(p1ThumbSrc);
// Click on p1's video
await context.p3.getFilmstrip().pinParticipant(context.p1);
// The avatar should be on large video and display name instead of an avatar, local video displayed
await context.p3.driver.waitUntil(
async () => await context.p3.getLargeVideoResource() === p1EndpointId, {
timeout: 2000,
timeoutMsg: `Large video did not switch to ${context.p1.name}`
});
await context.p3.assertDisplayNameVisibleOnStage(
await context.p3.getFilmstrip().getRemoteDisplayName(p1EndpointId));
// p2 has the default avatar
await context.p3.assertThumbnailShowsAvatar(context.p2, false, true);
await context.p3.assertThumbnailShowsAvatar(context.p3, true);
// Click on p2's video
await context.p3.getFilmstrip().pinParticipant(context.p2);
// The avatar should be on large video and display name instead of an avatar, local video displayed
await context.p3.driver.waitUntil(
async () => await context.p3.getLargeVideoResource() === p2EndpointId, {
timeout: 2000,
timeoutMsg: `Large video did not switch to ${context.p2.name}`
});
await context.p3.assertDisplayNameVisibleOnStage(
await context.p3.getFilmstrip().getRemoteDisplayName(p2EndpointId)
);
await context.p3.assertThumbnailShowsAvatar(context.p1, false, false, true);
await context.p3.assertThumbnailShowsAvatar(context.p3, true);
await context.p3.hangup();
// Unmute p1's and p2's videos
await context.p1.getToolbar().clickVideoUnmuteButton();
await context.p2.getParticipantsPane().assertVideoMuteIconIsDisplayed(context.p1, true);
await context.p1.getParticipantsPane().assertVideoMuteIconIsDisplayed(context.p1, true);
});
it('email persistence', async () => {
await context.p1.getToolbar().clickProfileButton();
expect(await context.p1.getSettingsDialog().getEmail()).toBe(EMAIL);
await context.p1.hangup();
await ensureTwoParticipants(context, {
skipDisplayName: true
});
await context.p1.getToolbar().clickProfileButton();
expect(await context.p1.getSettingsDialog().getEmail()).toBe(EMAIL);
});
});

View File

@@ -3,6 +3,7 @@ import { multiremotebrowser } from '@wdio/globals';
import { Buffer } from 'buffer';
import path from 'node:path';
import process from 'node:process';
import pretty from 'pretty';
import { getLogs, initLogger, logInfo } from './helpers/browserLogger';
import { IContext } from './helpers/types';
@@ -58,7 +59,7 @@ export const config: WebdriverIO.MultiremoteConfig = {
],
maxInstances: 1,
baseUrl: process.env.BASE_URL || 'https://alpha.jitsi.net/torture',
baseUrl: process.env.BASE_URL || 'https://alpha.jitsi.net/torture/',
tsConfigPath: './tsconfig.json',
// Default timeout for all waitForXXX commands.
@@ -249,7 +250,7 @@ export const config: WebdriverIO.MultiremoteConfig = {
AllureReporter.addAttachment(`console-logs-${instance}`, getLogs(bInstance) || '', 'text/plain');
allProcessing.push(bInstance.getPageSource().then(source => {
AllureReporter.addAttachment(`html-source-${instance}`, source, 'text/plain');
AllureReporter.addAttachment(`html-source-${instance}`, pretty(source), 'text/plain');
}));
});