|
| 1 | +import { expect } from 'chai' |
| 2 | +import MultipleElementsFound, { |
| 3 | + computeDepths, |
| 4 | + computeParents, |
| 5 | + formatTree, |
| 6 | + isAncestorXPath, |
| 7 | + splitXPath, |
| 8 | +} from '../../lib/helper/errors/MultipleElementsFound.js' |
| 9 | + |
| 10 | +function stubWebElement(xpath, html, shouldThrow) { |
| 11 | + return { |
| 12 | + toAbsoluteXPath: async () => { |
| 13 | + if (shouldThrow) throw new Error('detached') |
| 14 | + return xpath |
| 15 | + }, |
| 16 | + toSimplifiedHTML: async () => { |
| 17 | + if (shouldThrow) throw new Error('detached') |
| 18 | + return html |
| 19 | + }, |
| 20 | + } |
| 21 | +} |
| 22 | + |
| 23 | +describe('MultipleElementsFound tree formatting', () => { |
| 24 | + it('splits xpath into segments', () => { |
| 25 | + expect(splitXPath('//html/body/div[1]/span')).to.deep.equal(['html', 'body', 'div[1]', 'span']) |
| 26 | + expect(splitXPath('')).to.deep.equal([]) |
| 27 | + expect(splitXPath(null)).to.deep.equal([]) |
| 28 | + }) |
| 29 | + |
| 30 | + it('detects ancestor by segments, not string prefix', () => { |
| 31 | + expect(isAncestorXPath('//html/body/div[1]', '//html/body/div[1]/span')).to.equal(true) |
| 32 | + expect(isAncestorXPath('//html/body/div[1]', '//html/body/div[10]')).to.equal(false) |
| 33 | + expect(isAncestorXPath('//html/body/div[1]', '//html/body/div[1]')).to.equal(false) |
| 34 | + expect(isAncestorXPath('//html/body/div[1]/span', '//html/body/div[1]')).to.equal(false) |
| 35 | + expect(isAncestorXPath(null, '//html/body')).to.equal(false) |
| 36 | + }) |
| 37 | + |
| 38 | + it('keeps siblings at depth 0', () => { |
| 39 | + const entries = [ |
| 40 | + { index: 1, xpath: '//html/body/button[1]', html: '<button>1</button>' }, |
| 41 | + { index: 2, xpath: '//html/body/button[2]', html: '<button>2</button>' }, |
| 42 | + ] |
| 43 | + expect(computeDepths(entries)).to.deep.equal([0, 0]) |
| 44 | + }) |
| 45 | + |
| 46 | + it('indents children of a matched parent', () => { |
| 47 | + const entries = [ |
| 48 | + { index: 1, xpath: '//html/body/div[1]', html: '<div class="item">' }, |
| 49 | + { index: 2, xpath: '//html/body/div[1]/div[1]', html: '<div class="item">' }, |
| 50 | + { index: 3, xpath: '//html/body/div[1]/div[2]', html: '<div class="item">' }, |
| 51 | + ] |
| 52 | + expect(computeParents(entries)).to.deep.equal([-1, 0, 0]) |
| 53 | + expect(computeDepths(entries)).to.deep.equal([0, 1, 1]) |
| 54 | + const items = formatTree(entries, [0, 1, 1], [-1, 0, 0]) |
| 55 | + expect(items[0]).to.equal(' 1. > //html/body/div[1]\n <div class="item">') |
| 56 | + expect(items[1]).to.equal(' 2. (inside 1.) > //html/body/div[1]/div[1]\n <div class="item">') |
| 57 | + expect(items[2]).to.equal(' 3. (inside 1.) > //html/body/div[1]/div[2]\n <div class="item">') |
| 58 | + }) |
| 59 | + |
| 60 | + it('marks the immediate parent for deeper nesting', () => { |
| 61 | + const entries = [ |
| 62 | + { index: 1, xpath: '//html/body/div[1]', html: '<div>' }, |
| 63 | + { index: 2, xpath: '//html/body/div[1]/ul', html: '<ul>' }, |
| 64 | + { index: 3, xpath: '//html/body/div[1]/ul/li', html: '<li>' }, |
| 65 | + { index: 4, xpath: '//html/body/div[2]', html: '<div>' }, |
| 66 | + ] |
| 67 | + expect(computeParents(entries)).to.deep.equal([-1, 0, 1, -1]) |
| 68 | + expect(computeDepths(entries)).to.deep.equal([0, 1, 2, 0]) |
| 69 | + const items = formatTree(entries, [0, 1, 2, 0], [-1, 0, 1, -1]) |
| 70 | + expect(items[2]).to.include('3. (inside 2.) >') |
| 71 | + expect(items[3]).to.equal(' 4. > //html/body/div[2]\n <div>') |
| 72 | + }) |
| 73 | + |
| 74 | + it('renders failed lookups as roots and keeps global numbering', async () => { |
| 75 | + const err = new MultipleElementsFound('.item', [ |
| 76 | + stubWebElement('//html/body/div[1]', '<div class="item">'), |
| 77 | + stubWebElement(null, null, true), |
| 78 | + stubWebElement('//html/body/div[1]/div[1]', '<div class="item">'), |
| 79 | + ]) |
| 80 | + await err.fetchDetails() |
| 81 | + expect(err.message).to.include(' 1. > //html/body/div[1]') |
| 82 | + expect(err.message).to.include(' 2. [Unable to get element info: detached]') |
| 83 | + expect(err.message).to.include(' 3. (inside 1.) > //html/body/div[1]/div[1]') |
| 84 | + }) |
| 85 | + |
| 86 | + it('renders nested fetchDetails output with indentation', async () => { |
| 87 | + const err = new MultipleElementsFound('.item', [ |
| 88 | + stubWebElement('//html/body/div[1]', '<div class="item">'), |
| 89 | + stubWebElement('//html/body/div[1]/div[1]', '<div class="item">'), |
| 90 | + stubWebElement('//html/body/div[1]/div[2]', '<div class="item">'), |
| 91 | + ]) |
| 92 | + await err.fetchDetails() |
| 93 | + const lines = err.message.split('\n') |
| 94 | + expect(lines[1]).to.equal(' 1. > //html/body/div[1]') |
| 95 | + expect(lines[3]).to.equal(' 2. (inside 1.) > //html/body/div[1]/div[1]') |
| 96 | + expect(lines[5]).to.equal(' 3. (inside 1.) > //html/body/div[1]/div[2]') |
| 97 | + expect(err.message).to.include('Use a more specific locator') |
| 98 | + }) |
| 99 | +}) |
0 commit comments