How to use isSameVNodeType method in Playwright Internal

Best JavaScript code snippet using playwright-internal

patch.js

Source:patch.js Github

copy

Full Screen

...21 // 4. 否则挂载新的子节点22 // mountChildren(...)23 }24}25function isSameVNodeType(n1, n1) {26 //27}28export function patchkeyedChildren(c1, c2) {29 let i = 0;30 const l2 = c2.length;31 let e1 = c1.length - 1; // prev ending index32 let e2 = l2 - 1; // next ending index33 // i <= 2 && i <= 334 // 从左向右diff35 while (i <= e1 && i <= e2) {36 const n1 = c1[i];37 const n2 = c2[i];38 if (isSameVNodeType(n1, n2)) {39 // 如果是相同的节点类型,则进行递归patch40 // patch(...)41 } else {42 // 否则退出43 break;44 }45 i++;46 }47 // i <= 2 && i <= 348 // 从又向左diff49 // 结束后: e1 = 0 e2 = 150 while (i <= e1 && i <= e2) {51 const n1 = c1[e1];52 const n2 = c2[e2];53 if (isSameVNodeType(n1, n2)) {54 // 相同的节点类型55 // patch(...)56 } else {57 // 否则退出58 break;59 }60 e1--;61 e2--;62 }63 // 3. common sequence + mount64 // (a b)65 // (a b) c66 // i = 2, e1 = 1, e2 = 267 // (a b)68 // c (a b)69 // i = 0, e1 = -1, e2 = 070 if (i > e1) {71 if (i <= e2) {72 const nextPos = e2 + 1;73 // nextPos < l2,说明有已经patch过尾部节点,74 // 否则会获取父节点作为锚点75 const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor;76 while (i <= e2) {77 patch(null, c2[i], anchor, ...others);78 i++;79 }80 }81 }82 // 4. common sequence + unmount83 // (a b) c84 // (a b)85 // i = 2, e1 = 2, e2 = 186 // a (b c)87 // (b c)88 // i = 0, e1 = 0, e2 = -189 // 公共序列 卸载旧的90 else if (i > e2) {91 while (i <= e1) {92 unmount(c1[i], parentComponent, parentSuspense, true);93 i++;94 }95 }96 // 5. 乱序的情况97 // [i ... e1 + 1]: a b [c d e] f g98 // [i ... e2 + 1]: a b [e d c h] f g99 // i = 2, e1 = 4, e2 = 5100 const s1 = i; // s1 = 2101 const s2 = i; // s2 = 2102 // 5.1 build key:index map for newChildren103 // 首先为新的子节点构建在新的子序列中 key:index 的映射104 // 通过map 创建的新的子节点105 const keyToNewIndexMap = new Map();106 // 遍历新的节点,为新节点设置key107 // i = 2; i <= 5108 for (i = s2; i <= e2; i++) {109 // 获取的是新序列中的子节点110 const nextChild = c2[i];111 if (nextChild.key != null) {112 // nextChild.key 已存在113 // a b [e d c h] f g114 // e:2 d:3 c:4 h:5115 keyToNewIndexMap.set(nextChild.key, i);116 }117 }118 // 5.2 loop through old children left to be patched and try to patch119 // matching nodes & remove nodes that are no longer present120 // 从旧的子节点的左侧开始循环遍历进行patch。121 // 并且patch匹配的节点 并移除不存在的节点122 // 已经patch的节点个数123 let patched = 0;124 // 需要patch的节点数量125 // 以上图为例:e2 = 5; s2 = 2; 知道需要patch的节点个数126 // toBePatched = 4127 const toBePatched = e2 - s2 + 1;128 // 用于判断节点是否需要移动129 // 当新旧队列中出现可复用节点交叉时,moved = true130 let moved = false;131 // used to track whether any node has moved132 // 用于记录节点是否已经移动133 let maxNewIndexSoFar = 0;134 // works as Map<newIndex, oldIndex>135 // 作新旧节点的下标映射136 // Note that oldIndex is offset by +1137 // 注意 旧节点的 index 要向右偏移一个下标138 // and oldIndex = 0 is a special value indicating the new node has139 // no corresponding old node.140 // 并且旧节点Index = 0 是一个特殊的值,用于表示新的节点中没有对应的旧节点141 // used for determining longest stable subsequence142 // newIndexToOldIndexMap 用于确定最长递增子序列143 // 新下标与旧下标的map144 const newIndexToOldIndexMap = new Array(toBePatched);145 // 将所有的值初始化为0146 // [0, 0, 0, 0]147 for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;148 // 遍历未处理旧序列中子节点149 for (i = s1; i <= e1; i++) {150 // 获取旧节点151 // 会逐个获取 c d e152 const prevChild = c1[i];153 // 如果已经patch 的数量 >= 需要进行patch的节点个数154 // patched刚开始为 0155 // patched >= 4156 if (patched >= toBePatched) {157 // all new children have been patched so this can only be a removal158 // 这说明所有的新节点已经被patch 因此可以移除旧的159 unmount(prevChild, parentComponent, parentSuspense, true);160 continue;161 }162 }163 // 新节点下标164 let newIndex;165 if (prevChild.key != null) {166 // 旧的节点肯定有key,167 // 根据旧节点key 获取相同类型的新的子节点 在 新的队列中对应节点位置168 // 这个时候 因为c d e 是原来的节点 并且有key169 // h 是新增节点 旧节点中没有 获取不到 对应的index 会走else170 // 所以newIndex在开始时会有如下情况171 /**172 * node newIndex173 * c 4174 * d 3175 * e 2176 * */177 // 这里是可以获取到newIndex的178 newIndex = keyToNewIndexMap.get(prevChild.key);179 }180 // key-less node, try to locate a key-less node of the same type181 // 如果旧的节点没有key182 // 则会查找没有key的 且为相同类型的新节点在 新节点队列中 的位置183 // j = 2: j <= 5184 for (j = s2; j <= e2; j++) {185 if (186 newIndexToOldIndexMap[j - s2] === 0 &&187 // 判断是否是新旧节点是否相同188 isSameVNodeType(prevChild, c2[j])189 ) {190 // 获取到相同类型节点的下标191 newIndex = j;192 break;193 }194 }195 if (newIndex === undefined) {196 // 没有对应的新节点 卸载旧的197 unmount(prevChild, parentComponent, parentSuspense, true);198 }199 // 这里处理获取到newIndex的情况200 // 开始整理新节点下标 Index 对于 相同类型旧节点在 旧队列中的映射201 // 新节点下标从 s2=2 开始,对应的旧节点下标需要偏移一个下标202 // 0 表示当前节点没有对应的旧节点...

Full Screen

Full Screen

renderer.js

Source:renderer.js Github

copy

Full Screen

1import { createApp } from './createApp'2import { ShapeFlags } from '../shared/utils'3import { createComponentInstance, setupComponent } from './component'4import { effect } from '../reactivity'5export function createRenderer(options) {6 console.log(options)7 return baseCreateRenderer(options)8}9function baseCreateRenderer(options) {10 const {11 createElement: hostCreateElement,12 patchProp: hostPatchProp,13 setElementText: hostSetElementText,14 insetElement: hostInsert,15 removeChild: hostRemove16 } = options17 const mountElement = (vnode, container, anchor) => {18 let { shapeFlag, props } = vnode19 let el = vnode.el = hostCreateElement(vnode.type);20 // 创建子孙节点21 if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {22 hostSetElementText(el, vnode.children)23 } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {24 mountChildren(vnode.children, el)25 }26 if (props) {27 // 添加属性28 for (let key in props) {29 hostPatchProp(el, key, null, props[key])30 }31 }32 hostInsert(el, container, anchor)33 }34 const mountChildren = (children, container) => {35 for (let i = 0; i < children.length; i++) {36 patch(null, children[i], container)37 }38 }39 const patchProps = (oldProps, newProps, el) => {40 if (oldProps !== newProps) {41 // 新属性覆盖老属性42 for (let key in newProps) {43 const prev = oldProps[key],44 next = newProps[key];45 if (prev !== next) {46 hostPatchProp(el, key, prev, next)47 }48 }49 // 老得有属性,新的没有,需要删除属性50 for (let key in oldProps) {51 if (!(key in newProps)) {52 hostPatchProp(el, key, oldProps[key], null)53 }54 }55 }56 }57 const patchKeydChildren = (c1, c2, el) => {58 // 内部优化策略 头尾双指针59 // ab i=260 // abcd61 let i = 0;62 let e1 = c1.length - 1,63 e2 = c2.length - 1;64 // 从前往后65 while (i <= e1 && i <= e2) {66 const n1 = c1[i], n2 = c2[i];67 if (isSameVnodeType(n1, n2)) {68 patch(n1, n2, el)69 } else {70 break;71 }72 i++;73 }74 // 从后往前75 // abc76 // dabc77 while (i <= e1 && i <= e2) {78 const n1 = c1[e1], n2 = c2[e2];79 if (isSameVnodeType(n1, n2)) {80 patch(n1, n2, el)81 } else {82 break;83 }84 e1--;85 e2--;86 }87 // 只考虑元素新增和删除的情况88 // abc=>abcd (i=3,e1=2,e2=3) abc=>dabc (i=0 e1=-1 e2=0)89 // 规律:只要i>e1 新增元素90 if (i > e1) {91 // 新增部分92 if (i <= e2) {93 // 根据e2的下一个元素和数组长度比较94 const nextPos = e2 + 1;95 const anchor = nextPos < c2.length ? c2[nextPos].el : null;96 while (i <= e2) {97 patch(null, c2[i], el, anchor);98 i++;99 }100 }101 } else if (i > e2) {// abcd=>abc (i=3,e1=3,e2=2) 删除节点102 while (i <= e1) {103 hostRemove(c1[i].el)104 i++;105 }106 } else {107 // 无规律情况108 const s1 = i, s2 = i;109 // 新索引和key的一个映射110 const keyToNewIndexMap = new Map();111 for (let i = s2; i < e2; i++) {112 const nextchild = c2[i];113 keyToNewIndexMap.set(nextchild.key, i);114 }115 console.log(keyToNewIndexMap)116 const toBePatched = e2 - s2 + 1;117 const newIndexToOldMapIndex = new Array(toBePatched).fill(0);118 for (let i = s1; i <= e1; i++) {119 const prevChild = c1[i];120 let newIndex = keyToNewIndexMap.get(prevChild.key);121 if (newIndex == undefined) {122 // 删除123 hostRemove(prevChild.el)124 } else {125 newIndexToOldMapIndex[newIndex - s2] = i + 1;126 patch(prevChild, c2[newIndex], el)127 }128 }129 // 最长增长序列130 const increasingIndexSequence=getSequence(newIndexToOldMapIndex);131 let j=increasingIndexSequence.length-1;132 for (let i = toBePatched - 1; i >= 0; i--) {133 const nextIndex = s2 + i;134 const nextChild = c2[nextIndex];135 const anchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null;// 当前元素的下一个元素136 if (newIndexToOldMapIndex[i] === 0) {// 这是新元素,直接插入137 patch(null, nextChild, el, anchor)138 } else {139 // 下面方式需要反复移动元素,性能较差,可参考官方优化手段 利用render.ts中的getSequence方法140 // hostInsert(nextChild.el, el, anchor)141 // 按照vue3源码实现142 if(j<0 || i!=increasingIndexSequence[j]){143 hostInsert(nextChild.el, el, anchor)144 }else{145 j--146 }147 }148 }149 }150 }151 const patchChildren = (n1, n2, el) => {152 const c1 = n1.children,153 c2 = n2.children;154 const prevShapeFlag = n1.shapeFlag,155 shapeFlag = n2.shapeFlag;156 // 老节点是文本,新节点也是文本,直接覆盖157 // 老节点是数组,新节点是文本,直接覆盖158 if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {159 if (c2 !== c1) {160 hostSetElementText(el, c2)161 }162 } else {163 // 老节点是数组,新节点是数组,前后数组diff164 if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {165 console.log('核心diff')166 patchKeydChildren(c1, c2, el)167 } else {168 // 老节点是文本,新节点是数组169 if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {170 // 移除老的文本171 hostSetElementText(el, '')172 }173 if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {174 // 插入新的数组175 for (let i = 0; i < c2.length; i++) {176 patch(null, c2[i], el)177 }178 }179 }180 }181 }182 const patchElement = (n1, n2, container) => {183 console.log('元素更新')184 let el = (n2.el = n1.el);185 const oldProps = n1.props || {};186 const newProps = n2.props || {};187 patchProps(oldProps, newProps, el);188 patchChildren(n1, n2, el)189 }190 const processElement = (n1, n2, container, anchor) => {191 if (n1 === null) {192 // 初次渲染挂载193 mountElement(n2, container, anchor)194 } else {195 // 更新196 patchElement(n1, n2, container, anchor)197 }198 }199 const processComponent = (n1, n2, container) => {200 if (n1 === null) {201 // 初次渲染挂载202 mountComponent(n2, container)203 } else {204 // 更新205 updateComponent(n1, n2, container)206 }207 }208 // 组件mount209 const mountComponent = (initialVnode, container) => {210 // 步骤:1、创建组件实例。2、找到组件render方法3、执行render211 const instance = initialVnode.component = createComponentInstance(initialVnode);212 setupComponent(instance)213 // 给组件创建一个effect,用于渲染,类似于vue2 中渲染watcher214 setupRenderEffect(instance, initialVnode, container)215 }216 // 组件更新217 const updateComponent = (n1, n2, container) => {218 console.log('组件更新')219 }220 const setupRenderEffect = (instance, initialVnode, container) => {221 effect(function componentEffect() {222 if (!instance.isMounted) {223 // 保存渲染组件结果到subTree224 const subTree = instance.subTree = instance.render();225 patch(null, subTree, container)226 instance.isMounted = true;227 } else {228 // 更新操作229 let prev = instance.subTree;230 let next = instance.render();231 patch(prev, next, container)232 }233 })234 }235 const isSameVnodeType = (n1, n2) => {236 return n1.type === n2.type && n1.key === n2.key;237 }238 const patch = (n1, n2, container, anchor = null) => {239 const { shapeFlag } = n2;240 if (n1) {241 if (isSameVnodeType(n1, n2)) {242 // 节点相同,可以复用243 console.log('复用')244 } else {245 // 节点类型不同246 hostRemove(n1.el);247 n1 = null;248 }249 }250 if (shapeFlag & ShapeFlags.ELEMENT) {251 console.log('元素节点', container)252 processElement(n1, n2, container, anchor)253 } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {254 console.log('组件', container)255 processComponent(n1, n2, container)256 }257 }258 const render = (vnode, container) => {259 console.log('渲染器', vnode, container)260 patch(null, vnode, container)261 }262 return {263 createApp: createApp(render)264 }265}266// vue3 源码中实现,packages/runtime-core/src/renderer.ts267// https://en.wikipedia.org/wiki/Longest_increasing_subsequence268function getSequence(arr){269 const p = arr.slice()270 const result = [0]271 let i, j, u, v, c272 const len = arr.length273 for (i = 0; i < len; i++) {274 const arrI = arr[i]275 if (arrI !== 0) {276 j = result[result.length - 1]277 if (arr[j] < arrI) {278 p[i] = j279 result.push(i)280 continue281 }282 u = 0283 v = result.length - 1284 while (u < v) {285 c = ((u + v) / 2) | 0286 if (arr[result[c]] < arrI) {287 u = c + 1288 } else {289 v = c290 }291 }292 if (arrI < arr[result[u]]) {293 if (u > 0) {294 p[i] = result[u - 1]295 }296 result[u] = i297 }298 }299 }300 u = result.length301 v = result[u - 1]302 while (u-- > 0) {303 result[u] = v304 v = p[v]305 }306 return result...

Full Screen

Full Screen

index.js

Source:index.js Github

copy

Full Screen

1/**2 * 3 * vdom 虚拟节点4 * old 老的节点5 * new 新的节点6 * 7 * 8 * mountElement 新增元素9 * patch 复用元素10 * unmoubt 删除元素11 * move 元素移动12 * 13 * @param {*} c1 14 * @param {*} c2 15 * @param {*} param2 16 */17exports.diffArray = (c1, c2, { mountElement, patch, unmount, move }) => {18 // 默认同一层级19 function isSameVnodeType(n1, n2) {20 return n1.key === n2.key // && n1.type === n2.type 21 }22 let l1 = c1.length // 老节点的长度23 let l2 = c2.length // 新节点的长度24 let i = 025 let e1 = l1 - 1 // 老节点最后一个节点的索引26 let e2 = l2 - 1 // 新节点最后一个节点的索引27 // 1. 从左边查找 28 while (i <= e1 && i <= e2) {29 const n1 = c1[i]30 const n2 = c2[i]31 if (isSameVnodeType(n1, n2)) {32 patch(n1.key)33 } else {34 break35 }36 i++37 }38 // 2. 从右边开始查找39 while (i <= e1 && i <= e2) {40 const n1 = c1[e1]41 const n2 = c2[e2]42 if (isSameVnodeType(n1, n2)) {43 patch(n1.key)44 } else {45 break46 }47 e1--48 e2--49 }50 // 3. 老节点没有了, 新节点在 则新增51 if (i > e1) {52 if (i <= e2) {53 while (i <= e2) {54 const n2 = c2[i]55 mountElement(n2.key)56 i++57 }58 }59 }60 // 4. 老节点存在 新节点没有 则移除61 else if (i > e2) {62 if (i <= e1) {63 while (i <= e1) {64 const n1 = c1[i]65 unmount(n1.key)66 i++67 }68 }69 }70 // 5. 新老节点都有, 顺序不稳定71 else {72 const s1 = i73 const s2 = i74 // 剩下的就是中间乱序的数组进行对比,75 // 1.暴力解法 则双重遍历 时间复杂度 O(n)76 // 2.构建一个map映射表 每次遍历老的节点去这个映射表中去查找77 const keyToNewIndexMap = new Map()78 // {79 // e: 2,80 // c: 3,81 // d: 4, 82 // h: 583 // }84 for (i = s2; i <= e2; i++) {85 const nextChild = c2[i]86 keyToNewIndexMap.set(nextChild.key, i)87 }88 // 记录新节点有多少个需要处理89 const toBePatched = e2 - s2 + 190 let patched = 091 // 构建一个数组记录一下老的节点在新节点中的位置92 const newIndexToOldIndexMap = new Array(toBePatched)93 // 数组的下标记录的是新元素的相对下标94 // 数据的值如果是0 证明这个值是需要新增的95 // [5, 3, 4, 0]96 for (let i = 0; i < toBePatched; i++) {97 newIndexToOldIndexMap[i] = 098 }99 let moved = false100 let maxNewIndexSoFar = 0101 // 遍历老节点, 去map图中查找节点,确定是复用还是删除102 for (i = s1; i <= e1; i++) {103 const prevChild = c1[i]104 // newIndex 老节点在新节点中对应的索引105 const newIndex = keyToNewIndexMap.get(prevChild.key)106 // 如果老的节点在map图中没有找到,说明这个节点需要移除107 if (newIndex === undefined) {108 unmount(prevChild.key)109 } else {110 // maxNewIndexSoFar记录队伍最后一个元素的下标111 if (newIndex >= maxNewIndexSoFar) {112 maxNewIndexSoFar = newIndex113 } else {114 moved = true115 }116 // 如果老的节点在map图中找到了,则说明这个节点可以复用117 newIndexToOldIndexMap[newIndex - s2] = i + 1118 patch(prevChild.key)119 patched++120 }121 }122 // 遍历新元素 确定是新增还是移动123 // 获取最长递增子序列124 // [1,2]125 const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []126 let lastIndex = increasingNewIndexSequence.length - 1127 for (i = toBePatched - 1; i >= 0; i--) {128 // i是最新元素的相对下标129 const newChild = c2[s2 + i]130 // 判断节点是新增还是移动131 if (newIndexToOldIndexMap[i] === 0) {132 mountElement(newChild.key)133 } else {134 if (lastIndex < 0 || i !== increasingNewIndexSequence[lastIndex]) {135 move(newChild.key)136 } else {137 lastIndex--138 }139 }140 }141 }142 // 返回不需要移动的节点143 // 得到最长递增子序列lis(算法+实际应用,跳过0),返回路径144 function getSequence(arr) {145 // return [1, 2];146 // 最长递增子序列路径, 有序递增147 const lis = [0];148 // 相当于复制一份arr数组,此数组用于稍后纠正lis用的149 const recordIndexOfI = arr.slice();150 const len = arr.length;151 for (let i = 0; i < len; i++) {152 const arrI = arr[i];153 // 如果元素值为0,证明节点是新增的,老dom上没有,肯定不需要移动,所以跳过0,不在lis里154 if (arrI !== 0) {155 // 判断arrI插入到lis哪里156 const last = lis[lis.length - 1];157 // arrI比lis最后一个元素还大,又构成最长递增158 if (arr[last] < arrI) {159 // 记录第i次的时候,本来的元素是什么,后面要做回溯的160 recordIndexOfI[i] = last;161 lis.push(i);162 continue;163 }164 // 二分查找插入元素165 let left = 0,166 right = lis.length - 1;167 while (left < right) {168 const mid = (left + right) >> 1;169 // 0 1 2 3 4 (1.5)170 if (arr[lis[mid]] < arrI) {171 // mid< 目标元素 , 在右边172 left = mid + 1;173 } else {174 right = mid;175 }176 }177 if (arrI < arr[lis[left]]) {178 // 从lis中找到了比arrI大的元素里最小的那个,即arr[lis[left]]。179 // 否则则没有找到比arrI大的元素,就不需要做什么了180 if (left > 0) {181 // 记录第i次的时候,上次的元素的是什么,便于后面回溯182 recordIndexOfI[i] = lis[left - 1];183 }184 lis[left] = i;185 }186 }187 }188 // 遍历lis,纠正位置189 let i = lis.length;190 let last = lis[i - 1];191 while (i-- > 0) {192 lis[i] = last;193 last = recordIndexOfI[last];194 }195 return lis;196 }...

Full Screen

Full Screen

longestIncreasingSubsequence.js

Source:longestIncreasingSubsequence.js Github

copy

Full Screen

...17 * 判断两个节点是否相同 key 和 type18 * @param {*} n119 * @param {*} n220 */21 function isSameVNodeType(n1, n2) {22 // n1.key === n2.key && n1.type === n2.type;23 // 这里只模拟 key24 return n1.key === n2.key25 }26 let i = 027 // 新元素长度28 const l2 = c2.length29 // 最后一个老元素节点下标30 let e1 = c1.length - 131 // 最后一个新元素节点下标32 let e2 = l2 - 133 // 1. 左边按序查找, 如果节点不能复用,则停止34 while (i <= e1 && i <= e2) {35 const n1 = c1[i]36 const n2 = c2[i]37 if (isSameVNodeType(n1, n2)) {38 patch(n1.key)39 } else {40 break41 }42 i++43 }44 // 2. 右边按序查找,如果节点不能复用,则停止45 while (i <= e1 && i <= e2) {46 const n1 = c1[e1]47 const n2 = c2[e2]48 if (isSameVNodeType(n1, n2)) {49 patch(n1.key)50 } else {51 break52 }53 e1--54 e2--55 }56 // 3. 老节点没了57 if (i > e1) {58 // 新节点还有, 把新节点挂载到老节点59 if (i <= e2) {60 while (i <= e2) {61 const n2 = c2[i]62 mountElement(n2.key)...

Full Screen

Full Screen

vue-diff.js

Source:vue-diff.js Github

copy

Full Screen

1// dom2// old array a b c d e f g3// new array a b e c d h f g4// mountElement 新增元素 h5// patch 复用元素 a b c d e f g6// unmount 删除元素7// todo8// move 元素移动 ?9exports.diffArray = (c1, c2, { mountElement, patch, unmount, move }) => {10 function isSameVnodeType(n1, n2) {11 return n1.key === n2.key; //&& n1.type === n2.type;12 }13 let i = 0;14 const l1 = c1.length;15 const l2 = c2.length;16 let e1 = l1 - 1;17 let e2 = l2 - 1;18 // *1. 从左边往右查找,如果节点可以复用,则继续往右,不能就停止循环19 while (i <= e1 && i <= e2) {20 const n1 = c1[i];21 const n2 = c2[i];22 if (isSameVnodeType(n1, n2)) {23 patch(n1.key);24 } else {25 break;26 }27 i++;28 }29 // *2. 从右边往左边查找,如果节点可以复用,则继续往左,不能就停止循环30 while (i <= e1 && i <= e2) {31 const n1 = c1[e1];32 const n2 = c2[e2];33 if (isSameVnodeType(n1, n2)) {34 patch(n1.key);35 } else {36 break;37 }38 e1--;39 e2--;40 }41 // *3.1 老节点没了,新节点还有42 if (i > e1) {43 if (i <= e2) {44 while (i <= e2) {45 const n2 = c2[i];46 mountElement(n2.key);47 i++;48 }49 }50 }51 // *3.2 老节点还有,新节点没了52 else if (i > e2) {53 while (i <= e1) {54 const n1 = c1[i];55 unmount(n1.key);56 i++;57 }58 } else {59 // *4 新老节点都有,但是顺序不稳定60 // 遍历新老节点61 // i是新老元素的起始位置62 // *4.1 把新元素做成Map图,key: value(index)63 const s1 = i;64 const s2 = i;65 //66 const keyToNewIndexMap = new Map();67 for (i = s2; i <= e2; i++) {68 const nextChild = c2[i];69 keyToNewIndexMap.set(nextChild.key, i);70 }71 // *4.2 当前还有多少新元素要被patch(新增、更新)72 const toBePatched = e2 - s2 + 1;73 let patched = 0;74 const newIndexToOldIndexMap = new Array(toBePatched);75 // 下标是新元素的相对下标,值是老元素的下标+176 for (i = 0; i < toBePatched; i++) {77 newIndexToOldIndexMap[i] = 0;78 }79 // *4.3 先遍历老元素 检查老元素是否要被复用,如果复用就patch,如果不能复用就删除80 let moved = false;81 let maxNewIndexSoFar = 0;82 for (i = s1; i <= e1; i++) {83 const prevChild = c1[i];84 if (patched >= toBePatched) {85 unmount(prevChild.key);86 continue;87 }88 const newIndex = keyToNewIndexMap.get(prevChild.key);89 if (newIndex === undefined) {90 // 节点没法复用91 unmount(prevChild.key);92 } else {93 //移动发生在这里,这里的节点可能要被移动(ecd)94 if (newIndex >= maxNewIndexSoFar) {95 maxNewIndexSoFar = newIndex;96 } else {97 // 相对位置发生变化98 moved = true;99 }100 newIndexToOldIndexMap[newIndex - s2] = i + 1;101 patch(prevChild.key);102 patched++;103 }104 }105 // *4.4 遍历新元素 mount move106 // 返回不需要移动的节点107 const increasingNewIndexSequece = moved108 ? getSequence(newIndexToOldIndexMap)109 : [];110 let lastIndex = increasingNewIndexSequece.length - 1;111 // 相对下标112 for (i = toBePatched - 1; i >= 0; i--) {113 const nextChildIndex = s2 + i;114 const nextChild = c2[nextChildIndex];115 // 判断nextChild是mount还是move116 // 在老元素中出现的元素可能要move,没有出现过的要mount117 if (newIndexToOldIndexMap[i] === 0) {118 mountElement(nextChild.key);119 } else {120 // 可能move121 if (lastIndex < 0 || i !== increasingNewIndexSequece[lastIndex]) {122 move(nextChild.key);123 } else {124 lastIndex--;125 }126 }127 }128 }129 // 返回不需要移动的节点130 // 得到最长递增子序列lis(算法+实际应用,跳过0),返回路径131 function getSequence(arr) {132 // return [1, 2];133 // 最长递增子序列路径, 有序递增134 const lis = [0];135 // 相当于复制一份arr数组,此数组用于稍后纠正lis用的136 const recordIndexOfI = arr.slice();137 const len = arr.length;138 for (let i = 0; i < len; i++) {139 const arrI = arr[i];140 // 如果元素值为0,证明节点是新增的,老dom上没有,肯定不需要移动,所以跳过0,不在lis里141 if (arrI !== 0) {142 // 判断arrI插入到lis哪里143 const last = lis[lis.length - 1];144 // arrI比lis最后一个元素还大,又构成最长递增145 if (arr[last] < arrI) {146 // 记录第i次的时候,本来的元素是什么,后面要做回溯的147 recordIndexOfI[i] = last;148 lis.push(i);149 continue;150 }151 // 二分查找插入元素152 let left = 0,153 right = lis.length - 1;154 while (left < right) {155 const mid = (left + right) >> 1;156 // 0 1 2 3 4 (1.5)157 if (arr[lis[mid]] < arrI) {158 // mid< 目标元素 , 在右边159 left = mid + 1;160 } else {161 right = mid;162 }163 }164 if (arrI < arr[lis[left]]) {165 // 从lis中找到了比arrI大的元素里最小的那个,即arr[lis[left]]。166 // 否则则没有找到比arrI大的元素,就不需要做什么了167 if (left > 0) {168 // 记录第i次的时候,上次的元素的是什么,便于后面回溯169 recordIndexOfI[i] = lis[left - 1];170 }171 lis[left] = i;172 }173 }174 }175 // 遍历lis,纠正位置176 let i = lis.length;177 let last = lis[i - 1];178 while (i-- > 0) {179 lis[i] = last;180 last = recordIndexOfI[last];181 }182 return lis;183 }...

Full Screen

Full Screen

vue_diff.js

Source:vue_diff.js Github

copy

Full Screen

1// dom2// old array a b c d e f g3// new array a b e c d h f g4// mountElement 新增元素 h5// patch 复用元素 a b c d e f g6// unmount 删除元素7// todo8// move 元素移动 ?9exports.diffArray = (c1, c2, { mountElement, patch, unmount, move }) => {10 function isSameVnodeType(n1, n2) {11 return n1.key === n2.key; // && n1.type === n2.type12 }13 let i = 0;14 const l1 = c1.lenght;15 const l2 = c2.length;16 let e1 = l1 - 1; // 尾部坐标17 let e2 = l2 - 1;18 // 1. 从左往右查找复用元素(掐头)19 while (i <= e1 && i <= e2) {20 const n1 = c1[i];21 const n2 = c2[i];22 if (isSameVnodeType(n1, n2)) {23 patch(n1.key);24 } else {25 break;26 }27 i++;28 }29 // 2. 从右往左查找复用元素(去尾)30 while (i <= e1 && i <= e2) {31 const n1 = c1[i];32 const n2 = c2[i];33 if (isSameVnodeType(n1, n2)) {34 patch(n1.key);35 } else {36 break;37 }38 e1--;39 e2--;40 }41 // 3.1 老节点遍历完毕,还有新节点42 if (i > e1) {43 if (i <= e2) {44 while (i <= e2) {45 const n2 = c2[i];46 mountElement(n2.key);47 i++;48 }49 }50 }51 // 3.2 新节点遍历完毕还有老节点52 else if (i > e2) {53 if (i <= e1) {54 while (i <= e1) {55 const n1 = c1[i];56 unmount(n1.key);57 i++;58 }59 }60 }61 // 4. 新老节点都有,但是顺序不稳定62 // 遍历新老节点63 // i 是新老节点的起始位置64 else {65 // 4.1 需要记录相对位置关系,将新元素作为Map,key:value(index)66 const s1 = i;67 const s2 = i;68 const keyToNewIndexMap = new Map();69 for (i = s2; i <= e2; i++) {70 const nextChild = c2[i];71 keyToNewIndexMap.set(nextChild.key, i);72 }73 // 记录当前还有多少新元素需要被patch74 const toBePatched = e2 - s2 + 1;75 // 当前更新量76 let patched = 0;77 const newIndexToOldIndexMap = new Array(toBePatched);78 // 下标是新元素的相对下标,值是老元素的下标+179 for (i = 0; i < toBePatched; i++) {80 newIndexToOldIndexMap[i] = 0;81 }82 // *4.2 先遍历老元素 检查老元素是否要被复用,如果复用就patch,如果不能复用就删除83 let moved = false;84 let maxNewIndexSoFar = 0;85 for (i = s1; i <= e1; i++) {86 const prevChild = c1[i];87 // 如果需操作元素操作完毕,则删除剩余元素88 if (patched >= toBePatched) {89 unmount(prevChild.key);90 continue;91 }92 const newIndex = keyToNewIndexMap.get(prevChild.key);93 // 未复用94 if (newIndex === undefined) {95 unmount(prevChild.key);96 }97 // 复用了则需要移动98 else {99 if (newIndex >= maxNewIndexSoFar) {100 maxNewIndexSoFar = newIndex;101 } else {102 moved = true;103 }104 newIndexToOldIndexMap[newIndex - s2] = i + 1;105 patch(prevChild.key);106 patched++;107 }108 }109 // 4.3 遍历新元素 mount move110 // 返回不需要移动的节点111 const increasingNewIndexSequece = moved112 ? getSequence(newIndexToOldIndexMap)113 : [];114 let lastIndex = increasingNewIndexSequece.length - 1;115 // 相对下标116 for (i = toBePatched - 1; i >= 0; i--) {117 const nextChildIndex = s2 + i;118 const nextChild = c2[nextChildIndex];119 // 判断nextChild是mount还是move120 // 在老元素中出现的元素可能要move,没有出现过的要mount121 if (newIndexToOldIndexMap[i] === 0) {122 mountElement(nextChild.key);123 } else {124 // 可能move125 if (lastIndex < 0 || i !== increasingNewIndexSequece[lastIndex]) {126 move(nextChild.key);127 } else {128 lastIndex--;129 }130 }131 }132 }133 // 返回不需要移动的节点134 // 得到最长递增子序列lis(算法+实际应用,跳过0),返回路径135 function getSequence(arr) {136 // return [1, 2];137 // 最长递增子序列路径, 有序递增138 const lis = [0];139 // 相当于复制一份arr数组,此数组用于稍后纠正lis用的140 const recordIndexOfI = arr.slice();141 const len = arr.length;142 for (let i = 0; i < len; i++) {143 const arrI = arr[i];144 // 如果元素值为0,证明节点是新增的,老dom上没有,肯定不需要移动,所以跳过0,不在lis里145 if (arrI !== 0) {146 // 判断arrI插入到lis哪里147 const last = lis[lis.length - 1];148 // arrI比lis最后一个元素还大,又构成最长递增149 if (arr[last] < arrI) {150 // 记录第i次的时候,本来的元素是什么,后面要做回溯的151 recordIndexOfI[i] = last;152 lis.push(i);153 continue;154 }155 // 二分查找插入元素156 let left = 0,157 right = lis.length - 1;158 while (left < right) {159 const mid = (left + right) >> 1;160 // 0 1 2 3 4 (1.5)161 if (arr[lis[mid]] < arrI) {162 // mid< 目标元素 , 在右边163 left = mid + 1;164 } else {165 right = mid;166 }167 }168 if (arrI < arr[lis[left]]) {169 // 从lis中找到了比arrI大的元素里最小的那个,即arr[lis[left]]。170 // 否则则没有找到比arrI大的元素,就不需要做什么了171 if (left > 0) {172 // 记录第i次的时候,上次的元素的是什么,便于后面回溯173 recordIndexOfI[i] = lis[left - 1];174 }175 lis[left] = i;176 }177 }178 }179 // 遍历lis,纠正位置180 let i = lis.length;181 let last = lis[i - 1];182 while (i-- > 0) {183 lis[i] = last;184 last = recordIndexOfI[last];185 }186 return lis;187 }...

Full Screen

Full Screen

diff.js

Source:diff.js Github

copy

Full Screen

...14 let e2 = c2.length - 1;15 while (i <= e1 && i <= e2) {16 const prevChild = c1[i];17 const nextChild = c2[i];18 if (!isSameVNodeType(prevChild, nextChild)) {19 console.log("两个 child 不相等(从左往右比对)");20 break;21 }22 console.log("两个 child 相等,接下来对比着两个 child 节点(从左往右比对)");23 patch(prevChild, nextChild, container);24 i++;25 }26 while (i <= e1 && i <= e2) {27 // 从右向左取值28 const prevChild = c1[e1];29 const nextChild = c2[e2];30 if (!isSameVNodeType(prevChild, nextChild)) {31 console.log("两个 child 不相等(从右往左比对)");32 break;33 }34 console.log("两个 child 相等,接下来对比着两个 child 节点(从右往左比对)");35 patch(prevChild, nextChild, container);36 e1--;37 e2--;38 }39 if (i > e1 && i <= e2) {40 // 如果是这种情况的话就说明 e2 也就是新节点的数量大于旧节点的数量41 // 也就是说新增了 vnode42 // 应该循环 c243 while (i <= e2) {44 console.log(`需要新创建一个 vnode: ${c2[i].key}`);...

Full Screen

Full Screen

_v3_patch.js

Source:_v3_patch.js Github

copy

Full Screen

...11 const n1 = c1[i];12 const n2 = (c2[i] = optimized ?13 cloneIfMounted(c2[i]) :14 normalizeVNode(c2[i]));15 if (isSameVNodeType(n1, n2)) { // type and key are the same.16 patch(n1, n2, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);17 } else {18 break;19 }20 i++;21 }22 // 2. sync from end23 // a (b c)24 // d e (b c)25 while (i <= e1 && i <= e2) {26 const n1 = c1[e1];27 const n2 = (c2[e2] = optimized ?28 cloneIfMounted(c2[e2]) :29 normalizeVNode(c2[e2]));30 if (isSameVNodeType(n1, n2)) { // type and key are the same.31 patch(n1, n2, container, null, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);32 } else {33 break;34 }35 e1--;36 e2--;37 }38 // [Mars] : 2. Compare the head and the tail first.39 // 3. common sequence + mount40 // (a b)41 // (a b) c42 // i = 2, e1 = 1, e2 = 243 // (a b)44 // c (a b)45 // i = 0, e1 = -1, e2 = 046 if (i > e1) {47 if (i <= e2) {48 const nextPos = e2 + 1;49 const anchor = nextPos < l2 ? c2[nextPos].el : parentAnchor;50 while (i <= e2) {51 // mount new added vnodes not contained in oldChilds.52 patch(null, (c2[i] = optimized ?53 cloneIfMounted(c2[i]) :54 normalizeVNode(c2[i])), container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized);55 i++;56 }57 }58 }59 // 4. common sequence + unmount60 // (a b) c61 // (a b)62 // i = 2, e1 = 2, e2 = 163 // a (b c)64 // (b c)65 // i = 0, e1 = 0, e2 = -166 else if (i > e2) {67 while (i <= e1) {68 unmount(c1[i], parentComponent, parentSuspense, true);69 i++;70 }71 }72 // 5. unknown sequence73 // [i ... e1 + 1]: a b [c d e] f g74 // [i ... e2 + 1]: a b [e d c h] f g75 // i = 2, e1 = 4, e2 = 576 else {77 const s1 = i; // prev starting index78 const s2 = i; // next starting index79 // 5.1 build key:index map for newChildren80 const keyToNewIndexMap = new Map(); // use hashMap, cause it's easy to search an index of a key.81 for (i = s2; i <= e2; i++) {82 const nextChild = (c2[i] = optimized ?83 cloneIfMounted(c2[i]) :84 normalizeVNode(c2[i]));85 if (nextChild.key != null) {86 if (keyToNewIndexMap.has(nextChild.key)) {87 warn$1(`Duplicate keys found during update:`, JSON.stringify(nextChild.key), `Make sure keys are unique.`);88 }89 keyToNewIndexMap.set(nextChild.key, i);90 }91 }92 // 5.2 loop through old children left to be patched and try to patch93 // matching nodes & remove nodes that are no longer present94 let j;95 let patched = 0;96 const toBePatched = e2 - s2 + 1;97 let moved = false;98 // used to track whether any node has moved99 let maxNewIndexSoFar = 0;100 // works as Map<newIndex, oldIndex>101 // Note that oldIndex is offset by +1102 // and oldIndex = 0 is a special value indicating the new node has103 // no corresponding old node.104 // used for determining longest stable subsequence105 const newIndexToOldIndexMap = new Array(toBePatched);106 for (i = 0; i < toBePatched; i++)107 newIndexToOldIndexMap[i] = 0;108 for (i = s1; i <= e1; i++) {109 const prevChild = c1[i];110 if (patched >= toBePatched) {111 // all new children have been patched so this can only be a removal112 unmount(prevChild, parentComponent, parentSuspense, true);113 continue;114 }115 let newIndex;116 if (prevChild.key != null) {117 newIndex = keyToNewIndexMap.get(prevChild.key);118 } else {119 // key-less node, try to locate a key-less node of the same type120 for (j = s2; j <= e2; j++) {121 if (newIndexToOldIndexMap[j - s2] === 0 &&122 isSameVNodeType(prevChild, c2[j])) { // type and key are the same.123 newIndex = j;124 break;125 }126 }127 }128 if (newIndex === undefined) {129 unmount(prevChild, parentComponent, parentSuspense, true);130 } else {131 newIndexToOldIndexMap[newIndex - s2] = i + 1; // n:[h,c,d,e] o:[c,d,e,h] -> newIndexToOldIndexMap: [4,1,2,3]132 z if (newIndex >= maxNewIndexSoFar) {133 maxNewIndexSoFar = newIndex;134 } else {135 moved = true;136 }...

Full Screen

Full Screen

Using AI Code Generation

copy

Full Screen

1const { isSameVNodeType } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');2const { createVNode } = require('playwright/lib/server/supplements/recorder/vnode.js');3const vNode1 = createVNode('div', { 'data-test-id': 'test' }, []);4const vNode2 = createVNode('div', { 'data-test-id': 'test' }, []);5const vNode3 = createVNode('div', { 'data-test-id': 'test' }, [createVNode('span', {}, [])]);6const vNode4 = createVNode('div', { 'data-test-id': 'test' }, [createVNode('span', {}, [])]);7const vNode5 = createVNode('div', { 'data-test-id': 'test' }, [createVNode('span', { 'data-test-id': 'test' }, [])]);8const vNode6 = createVNode('div', { 'data-test-id': 'test' }, [createVNode('div', {}, [])]);9const { isSameVNodeType } = require('playwright/lib/server/supplements/recorder/recorderSupplement.js');10const { createVNode } = require('playwright/lib/server/supplements/recorder/vnode.js');11const vNode1 = createVNode('div', { 'data-test-id': 'test' }, []);12const vNode2 = createVNode('div', { 'data-test-id': 'test' }, []);13const vNode3 = createVNode('div', { 'data-test-id': 'test' }, [createVNode('span', {}, [])]);

Full Screen

Using AI Code Generation

copy

Full Screen

1const playwright = require('playwright');2const { isSameVNodeType } = require('playwright/lib/server/dom.js');3const { chromium } = require('playwright');4(async () => {5 const browser = await chromium.launch();6 const context = await browser.newContext();7 const page = await context.newPage();8 const htmlElement = await elementHandle._elementHandle._adoptIfNeeded();9 const isSameVNodeTypeResult = await isSameVNodeType(htmlElement, 'h1');10 console.log(isSameVNodeTypeResult);11 await browser.close();12})();

Full Screen

Using AI Code Generation

copy

Full Screen

1const { isSameVNodeType } = require('playwright/lib/server/common/dom.js');2const { parseHTML } = require('playwright/lib/server/common/html.js');3const { parseSelector } = require('playwright/lib/server/common/selectors2.js');4const { Node } = require('playwright/lib/server/common/dom.js');5`;6const document = parseHTML(html);7const { root } = document;8const { firstChild } = root;9const selector = parseSelector('.c');10const { node } = selector.match(root)[0];11console.log(isSameVNodeType(node, firstChild));12console.log(isSameVNodeType(node, node));

Full Screen

Using AI Code Generation

copy

Full Screen

1const { isSameVNodeType } = require('playwright/lib/server/dom.js');2const vNode1 = {3 attributes: { 'data-foo': 'bar' },4};5const vNode2 = {6 attributes: { 'data-foo': 'bar' },7};8const vNode3 = {9 attributes: { 'data-foo': 'bar' },10};11const vNode4 = {12 attributes: { 'data-foo': 'bar' },13};14const vNode5 = {15 attributes: { 'data-foo': 'bar' },16};17const addBtn = await page.$(‘button:has-text(“Add”)’);

Full Screen

Using AI Code Generation

copy

Full Screen

1const { isSameVNodeType } = require('playwright/lib/server/domServer');2const { parse } = require('playwright/lib/server/domParser');3const node1 = parse(`4`);5const node2 = parse(`6`);7console.log(isSameVNodeType(node1, node2));

Full Screen

Using AI Code Generation

copy

Full Screen

1import { isSameVNodeType } from 'playwright/lib/server/dom';2import { Page } from 'playwright';3const page: Page = await browser.newPage();4await page.setContent(`<input type="text" />`);5const input = await page.$('input');6const handle = await input.asElement();7const element = handle._node;8const parent = element.parentElement;9const firstChild = parent.firstChild;10const firstChildHandle = firstChild._elementHandle;11const firstChildElement = firstChildHandle._node;12console.log(isSameVNodeType(element, firstChildElement));13await page.close();14I am trying to implement a method that will allow me to compare 2 elements to see if they are the same element. I am using the method isSameVNodeType() in the Playwright Internal. This method is not exposed to use by Playwright users. I am using this method to compare the element I am trying to find and the element I am trying to compare it to. I am getting the element I am trying to compare to by using page.$eval() to get the first element that matches the selector. This method returns the element as a string. I am getting the element I am trying to find by using page.$() to get the element. This method returns the element as an ElementHandle. I am then using the ElementHandle.asElement() method to get the handle of the element. I am then using the handle._node method to get the element node of the element. I am then using the method isSameVNodeType() to compare the elements. I am getting the result that the elements are not the same. I am not sure why I am getting this result. I am trying to compare the same elements. I am trying to compare the input element and the first child element. I am getting the same result when I compare the input element and the

Full Screen

Playwright tutorial

LambdaTest’s Playwright tutorial will give you a broader idea about the Playwright automation framework, its unique features, and use cases with examples to exceed your understanding of Playwright testing. This tutorial will give A to Z guidance, from installing the Playwright framework to some best practices and advanced concepts.

Chapters:

  1. What is Playwright : Playwright is comparatively new but has gained good popularity. Get to know some history of the Playwright with some interesting facts connected with it.
  2. How To Install Playwright : Learn in detail about what basic configuration and dependencies are required for installing Playwright and run a test. Get a step-by-step direction for installing the Playwright automation framework.
  3. Playwright Futuristic Features: Launched in 2020, Playwright gained huge popularity quickly because of some obliging features such as Playwright Test Generator and Inspector, Playwright Reporter, Playwright auto-waiting mechanism and etc. Read up on those features to master Playwright testing.
  4. What is Component Testing: Component testing in Playwright is a unique feature that allows a tester to test a single component of a web application without integrating them with other elements. Learn how to perform Component testing on the Playwright automation framework.
  5. Inputs And Buttons In Playwright: Every website has Input boxes and buttons; learn about testing inputs and buttons with different scenarios and examples.
  6. Functions and Selectors in Playwright: Learn how to launch the Chromium browser with Playwright. Also, gain a better understanding of some important functions like “BrowserContext,” which allows you to run multiple browser sessions, and “newPage” which interacts with a page.
  7. Handling Alerts and Dropdowns in Playwright : Playwright interact with different types of alerts and pop-ups, such as simple, confirmation, and prompt, and different types of dropdowns, such as single selector and multi-selector get your hands-on with handling alerts and dropdown in Playright testing.
  8. Playwright vs Puppeteer: Get to know about the difference between two testing frameworks and how they are different than one another, which browsers they support, and what features they provide.
  9. Run Playwright Tests on LambdaTest: Playwright testing with LambdaTest leverages test performance to the utmost. You can run multiple Playwright tests in Parallel with the LammbdaTest test cloud. Get a step-by-step guide to run your Playwright test on the LambdaTest platform.
  10. Playwright Python Tutorial: Playwright automation framework support all major languages such as Python, JavaScript, TypeScript, .NET and etc. However, there are various advantages to Python end-to-end testing with Playwright because of its versatile utility. Get the hang of Playwright python testing with this chapter.
  11. Playwright End To End Testing Tutorial: Get your hands on with Playwright end-to-end testing and learn to use some exciting features such as TraceViewer, Debugging, Networking, Component testing, Visual testing, and many more.
  12. Playwright Video Tutorial: Watch the video tutorials on Playwright testing from experts and get a consecutive in-depth explanation of Playwright automation testing.

Run Playwright Internal automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

Try LambdaTest Now !!

Get 100 minutes of automation test minutes FREE!!

Next-Gen App & Browser Testing Cloud

Was this article helpful?

Helpful

NotHelpful