• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lancedikson / bowser / #667

22 Nov 2025 02:44PM UTC coverage: 90.347% (-0.4%) from 90.783%
#667

push

github

web-flow
feat: add more bot platforms (#542)

Co-authored-by: naorpeled <me@naor.dev>

331 of 421 branches covered (78.62%)

Branch coverage included in aggregate %.

36 of 38 new or added lines in 2 files covered. (94.74%)

633 of 646 relevant lines covered (97.99%)

290.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.86
/src/parser-browsers.js
1
/**
2
 * Browsers' descriptors
3
 *
4
 * The idea of descriptors is simple. You should know about them two simple things:
5
 * 1. Every descriptor has a method or property called `test` and a `describe` method.
6
 * 2. Order of descriptors is important.
7
 *
8
 * More details:
9
 * 1. Method or property `test` serves as a way to detect whether the UA string
10
 * matches some certain browser or not. The `describe` method helps to make a result
11
 * object with params that show some browser-specific things: name, version, etc.
12
 * 2. Order of descriptors is important because a Parser goes through them one by one
13
 * in course. For example, if you insert Chrome's descriptor as the first one,
14
 * more then a half of browsers will be described as Chrome, because they will pass
15
 * the Chrome descriptor's test.
16
 *
17
 * Descriptor's `test` could be a property with an array of RegExps, where every RegExp
18
 * will be applied to a UA string to test it whether it matches or not.
19
 * If a descriptor has two or more regexps in the `test` array it tests them one by one
20
 * with a logical sum operation. Parser stops if it has found any RegExp that matches the UA.
21
 *
22
 * Or `test` could be a method. In that case it gets a Parser instance and should
23
 * return true/false to get the Parser know if this browser descriptor matches the UA or not.
24
 */
25

26
import Utils from './utils.js';
27

28
const commonVersionIdentifier = /version\/(\d+(\.?_?\d+)+)/i;
6✔
29

30
const browsersList = [
6✔
31
  /* Googlebot */
32
  {
33
    test: [/googlebot/i],
34
    describe(ua) {
35
      const browser = {
6✔
36
        name: 'Googlebot',
37
      };
38
      const version = Utils.getFirstMatch(/googlebot\/(\d+(\.\d+))/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
6!
39

40
      if (version) {
6!
41
        browser.version = version;
6✔
42
      }
43

44
      return browser;
6✔
45
    },
46
  },
47

48
  /* AmazonBot */
49
  {
50
    test: [/amazonbot/i],
51
    describe(ua) {
52
      const browser = {
2✔
53
        name: 'AmazonBot',
54
      };
55
      const version = Utils.getFirstMatch(/amazonbot\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
2!
56

57
      if (version) {
2!
58
        browser.version = version;
2✔
59
      }
60

61
      return browser;
2✔
62
    },
63
  },
64

65
  /* BingCrawler */
66
  {
67
    test: [/bingbot/i],
68
    describe(ua) {
69
      const browser = {
6✔
70
        name: 'BingCrawler',
71
      };
72
      const version = Utils.getFirstMatch(/bingbot\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
6!
73

74
      if (version) {
6!
75
        browser.version = version;
6✔
76
      }
77

78
      return browser;
6✔
79
    },
80
  },
81

82
  /* BaiduSpider */
83
  {
84
    test: [/baiduspider/i],
85
    describe(ua) {
86
      const browser = {
2✔
87
        name: 'BaiduSpider',
88
      };
89
      const version = Utils.getFirstMatch(/baiduspider\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
2✔
90

91
      if (version) {
2!
NEW
92
        browser.version = version;
×
93
      }
94

95
      return browser;
2✔
96
    },
97
  },
98

99
  /* DuckDuckBot */
100
  {
101
    test: [/duckduckbot/i],
102
    describe(ua) {
103
      const browser = {
2✔
104
        name: 'DuckDuckBot',
105
      };
106
      const version = Utils.getFirstMatch(/duckduckbot\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
2!
107

108
      if (version) {
2!
109
        browser.version = version;
2✔
110
      }
111

112
      return browser;
2✔
113
    },
114
  },
115

116
  /* InternetArchiveCrawler */
117
  {
118
    test: [/ia_archiver/i],
119
    describe(ua) {
120
      const browser = {
2✔
121
        name: 'InternetArchiveCrawler',
122
      };
123
      const version = Utils.getFirstMatch(/ia_archiver\/(\d+(\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
2✔
124

125
      if (version) {
2!
NEW
126
        browser.version = version;
×
127
      }
128

129
      return browser;
2✔
130
    },
131
  },
132

133
  /* MetaWebCrawler */
134
  {
135
    test: [/facebookexternalhit/i, /facebookcatalog/i],
136
    describe() {
137
      return {
6✔
138
        name: 'MetaWebCrawler',
139
      };
140
    },
141
  },
142

143
  /* YahooSlurp */
144
  {
145
    test: [/yahoo!?[\s/]*slurp/i],
146
    describe() {
147
      return {
2✔
148
        name: 'YahooSlurp',
149
      };
150
    },
151
  },
152

153
  /* YandexBot */
154
  {
155
    test: [/yandexbot/i, /yandexmobilebot/i],
156
    describe() {
157
      return {
4✔
158
        name: 'YandexBot',
159
      };
160
    },
161
  },
162

163
  /* PingdomBot */
164
  {
165
    test: [/pingdom/i],
166
    describe() {
167
      return {
2✔
168
        name: 'PingdomBot',
169
      };
170
    },
171
  },
172

173
  /* Opera < 13.0 */
174
  {
175
    test: [/opera/i],
176
    describe(ua) {
177
      const browser = {
26✔
178
        name: 'Opera',
179
      };
180
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:opera)[\s/](\d+(\.?_?\d+)+)/i, ua);
26✔
181

182
      if (version) {
26!
183
        browser.version = version;
26✔
184
      }
185

186
      return browser;
26✔
187
    },
188
  },
189

190
  /* Opera > 13.0 */
191
  {
192
    test: [/opr\/|opios/i],
193
    describe(ua) {
194
      const browser = {
32✔
195
        name: 'Opera',
196
      };
197
      const version = Utils.getFirstMatch(/(?:opr|opios)[\s/](\S+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
32!
198

199
      if (version) {
32!
200
        browser.version = version;
32✔
201
      }
202

203
      return browser;
32✔
204
    },
205
  },
206
  {
207
    test: [/SamsungBrowser/i],
208
    describe(ua) {
209
      const browser = {
2✔
210
        name: 'Samsung Internet for Android',
211
      };
212
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:SamsungBrowser)[\s/](\d+(\.?_?\d+)+)/i, ua);
2✔
213

214
      if (version) {
2!
215
        browser.version = version;
2✔
216
      }
217

218
      return browser;
2✔
219
    },
220
  },
221
  {
222
    test: [/Whale/i],
223
    describe(ua) {
224
      const browser = {
2✔
225
        name: 'NAVER Whale Browser',
226
      };
227
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:whale)[\s/](\d+(?:\.\d+)+)/i, ua);
2✔
228

229
      if (version) {
2!
230
        browser.version = version;
2✔
231
      }
232

233
      return browser;
2✔
234
    },
235
  },
236
  {
237
    test: [/PaleMoon/i],
238
    describe(ua) {
239
      const browser = {
4✔
240
        name: 'Pale Moon',
241
      };
242
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:PaleMoon)[\s/](\d+(?:\.\d+)+)/i, ua);
4✔
243

244
      if (version) {
4!
245
        browser.version = version;
4✔
246
      }
247

248
      return browser;
4✔
249
    },
250
  },
251
  {
252
    test: [/MZBrowser/i],
253
    describe(ua) {
254
      const browser = {
2✔
255
        name: 'MZ Browser',
256
      };
257
      const version = Utils.getFirstMatch(/(?:MZBrowser)[\s/](\d+(?:\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
2!
258

259
      if (version) {
2!
260
        browser.version = version;
2✔
261
      }
262

263
      return browser;
2✔
264
    },
265
  },
266
  {
267
    test: [/focus/i],
268
    describe(ua) {
269
      const browser = {
4✔
270
        name: 'Focus',
271
      };
272
      const version = Utils.getFirstMatch(/(?:focus)[\s/](\d+(?:\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
4!
273

274
      if (version) {
4!
275
        browser.version = version;
4✔
276
      }
277

278
      return browser;
4✔
279
    },
280
  },
281
  {
282
    test: [/swing/i],
283
    describe(ua) {
284
      const browser = {
2✔
285
        name: 'Swing',
286
      };
287
      const version = Utils.getFirstMatch(/(?:swing)[\s/](\d+(?:\.\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
2!
288

289
      if (version) {
2!
290
        browser.version = version;
2✔
291
      }
292

293
      return browser;
2✔
294
    },
295
  },
296
  {
297
    test: [/coast/i],
298
    describe(ua) {
299
      const browser = {
2✔
300
        name: 'Opera Coast',
301
      };
302
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:coast)[\s/](\d+(\.?_?\d+)+)/i, ua);
2✔
303

304
      if (version) {
2!
305
        browser.version = version;
2✔
306
      }
307

308
      return browser;
2✔
309
    },
310
  },
311
  {
312
    test: [/opt\/\d+(?:.?_?\d+)+/i],
313
    describe(ua) {
314
      const browser = {
2✔
315
        name: 'Opera Touch',
316
      };
317
      const version = Utils.getFirstMatch(/(?:opt)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
2!
318

319
      if (version) {
2!
320
        browser.version = version;
2✔
321
      }
322

323
      return browser;
2✔
324
    },
325
  },
326
  {
327
    test: [/yabrowser/i],
328
    describe(ua) {
329
      const browser = {
6✔
330
        name: 'Yandex Browser',
331
      };
332
      const version = Utils.getFirstMatch(/(?:yabrowser)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
6!
333

334
      if (version) {
6!
335
        browser.version = version;
6✔
336
      }
337

338
      return browser;
6✔
339
    },
340
  },
341
  {
342
    test: [/ucbrowser/i],
343
    describe(ua) {
344
      const browser = {
6✔
345
        name: 'UC Browser',
346
      };
347
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:ucbrowser)[\s/](\d+(\.?_?\d+)+)/i, ua);
6✔
348

349
      if (version) {
6!
350
        browser.version = version;
6✔
351
      }
352

353
      return browser;
6✔
354
    },
355
  },
356
  {
357
    test: [/Maxthon|mxios/i],
358
    describe(ua) {
359
      const browser = {
4✔
360
        name: 'Maxthon',
361
      };
362
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:Maxthon|mxios)[\s/](\d+(\.?_?\d+)+)/i, ua);
4✔
363

364
      if (version) {
4!
365
        browser.version = version;
4✔
366
      }
367

368
      return browser;
4✔
369
    },
370
  },
371
  {
372
    test: [/epiphany/i],
373
    describe(ua) {
374
      const browser = {
2✔
375
        name: 'Epiphany',
376
      };
377
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:epiphany)[\s/](\d+(\.?_?\d+)+)/i, ua);
2!
378

379
      if (version) {
2!
380
        browser.version = version;
2✔
381
      }
382

383
      return browser;
2✔
384
    },
385
  },
386
  {
387
    test: [/puffin/i],
388
    describe(ua) {
389
      const browser = {
2✔
390
        name: 'Puffin',
391
      };
392
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:puffin)[\s/](\d+(\.?_?\d+)+)/i, ua);
2✔
393

394
      if (version) {
2!
395
        browser.version = version;
2✔
396
      }
397

398
      return browser;
2✔
399
    },
400
  },
401
  {
402
    test: [/sleipnir/i],
403
    describe(ua) {
404
      const browser = {
2✔
405
        name: 'Sleipnir',
406
      };
407
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:sleipnir)[\s/](\d+(\.?_?\d+)+)/i, ua);
2✔
408

409
      if (version) {
2!
410
        browser.version = version;
2✔
411
      }
412

413
      return browser;
2✔
414
    },
415
  },
416
  {
417
    test: [/k-meleon/i],
418
    describe(ua) {
419
      const browser = {
2✔
420
        name: 'K-Meleon',
421
      };
422
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/(?:k-meleon)[\s/](\d+(\.?_?\d+)+)/i, ua);
2✔
423

424
      if (version) {
2!
425
        browser.version = version;
2✔
426
      }
427

428
      return browser;
2✔
429
    },
430
  },
431
  {
432
    test: [/micromessenger/i],
433
    describe(ua) {
434
      const browser = {
8✔
435
        name: 'WeChat',
436
      };
437
      const version = Utils.getFirstMatch(/(?:micromessenger)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
8!
438

439
      if (version) {
8!
440
        browser.version = version;
8✔
441
      }
442

443
      return browser;
8✔
444
    },
445
  },
446
  {
447
    test: [/qqbrowser/i],
448
    describe(ua) {
449
      const browser = {
12✔
450
        name: (/qqbrowserlite/i).test(ua) ? 'QQ Browser Lite' : 'QQ Browser',
12✔
451
      };
452
      const version = Utils.getFirstMatch(/(?:qqbrowserlite|qqbrowser)[/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
12!
453

454
      if (version) {
12!
455
        browser.version = version;
12✔
456
      }
457

458
      return browser;
12✔
459
    },
460
  },
461
  {
462
    test: [/msie|trident/i],
463
    describe(ua) {
464
      const browser = {
42✔
465
        name: 'Internet Explorer',
466
      };
467
      const version = Utils.getFirstMatch(/(?:msie |rv:)(\d+(\.?_?\d+)+)/i, ua);
42✔
468

469
      if (version) {
42!
470
        browser.version = version;
42✔
471
      }
472

473
      return browser;
42✔
474
    },
475
  },
476
  {
477
    test: [/\sedg\//i],
478
    describe(ua) {
479
      const browser = {
4✔
480
        name: 'Microsoft Edge',
481
      };
482

483
      const version = Utils.getFirstMatch(/\sedg\/(\d+(\.?_?\d+)+)/i, ua);
4✔
484

485
      if (version) {
4!
486
        browser.version = version;
4✔
487
      }
488

489
      return browser;
4✔
490
    },
491
  },
492
  {
493
    test: [/edg([ea]|ios)/i],
494
    describe(ua) {
495
      const browser = {
10✔
496
        name: 'Microsoft Edge',
497
      };
498

499
      const version = Utils.getSecondMatch(/edg([ea]|ios)\/(\d+(\.?_?\d+)+)/i, ua);
10✔
500

501
      if (version) {
10!
502
        browser.version = version;
10✔
503
      }
504

505
      return browser;
10✔
506
    },
507
  },
508
  {
509
    test: [/vivaldi/i],
510
    describe(ua) {
511
      const browser = {
4✔
512
        name: 'Vivaldi',
513
      };
514
      const version = Utils.getFirstMatch(/vivaldi\/(\d+(\.?_?\d+)+)/i, ua);
4✔
515

516
      if (version) {
4!
517
        browser.version = version;
4✔
518
      }
519

520
      return browser;
4✔
521
    },
522
  },
523
  {
524
    test: [/seamonkey/i],
525
    describe(ua) {
526
      const browser = {
6✔
527
        name: 'SeaMonkey',
528
      };
529
      const version = Utils.getFirstMatch(/seamonkey\/(\d+(\.?_?\d+)+)/i, ua);
6✔
530

531
      if (version) {
6!
532
        browser.version = version;
6✔
533
      }
534

535
      return browser;
6✔
536
    },
537
  },
538
  {
539
    test: [/sailfish/i],
540
    describe(ua) {
541
      const browser = {
4✔
542
        name: 'Sailfish',
543
      };
544

545
      const version = Utils.getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i, ua);
4✔
546

547
      if (version) {
4!
548
        browser.version = version;
4✔
549
      }
550

551
      return browser;
4✔
552
    },
553
  },
554
  {
555
    test: [/silk/i],
556
    describe(ua) {
557
      const browser = {
8✔
558
        name: 'Amazon Silk',
559
      };
560
      const version = Utils.getFirstMatch(/silk\/(\d+(\.?_?\d+)+)/i, ua);
8✔
561

562
      if (version) {
8!
563
        browser.version = version;
8✔
564
      }
565

566
      return browser;
8✔
567
    },
568
  },
569
  {
570
    test: [/phantom/i],
571
    describe(ua) {
572
      const browser = {
2✔
573
        name: 'PhantomJS',
574
      };
575
      const version = Utils.getFirstMatch(/phantomjs\/(\d+(\.?_?\d+)+)/i, ua);
2✔
576

577
      if (version) {
2!
578
        browser.version = version;
2✔
579
      }
580

581
      return browser;
2✔
582
    },
583
  },
584
  {
585
    test: [/slimerjs/i],
586
    describe(ua) {
587
      const browser = {
2✔
588
        name: 'SlimerJS',
589
      };
590
      const version = Utils.getFirstMatch(/slimerjs\/(\d+(\.?_?\d+)+)/i, ua);
2✔
591

592
      if (version) {
2!
593
        browser.version = version;
2✔
594
      }
595

596
      return browser;
2✔
597
    },
598
  },
599
  {
600
    test: [/blackberry|\bbb\d+/i, /rim\stablet/i],
601
    describe(ua) {
602
      const browser = {
20✔
603
        name: 'BlackBerry',
604
      };
605
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/blackberry[\d]+\/(\d+(\.?_?\d+)+)/i, ua);
20✔
606

607
      if (version) {
20!
608
        browser.version = version;
20✔
609
      }
610

611
      return browser;
20✔
612
    },
613
  },
614
  {
615
    test: [/(web|hpw)[o0]s/i],
616
    describe(ua) {
617
      const browser = {
14✔
618
        name: 'WebOS Browser',
619
      };
620
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua) || Utils.getFirstMatch(/w(?:eb)?[o0]sbrowser\/(\d+(\.?_?\d+)+)/i, ua);
14✔
621

622
      if (version) {
14✔
623
        browser.version = version;
12✔
624
      }
625

626
      return browser;
14✔
627
    },
628
  },
629
  {
630
    test: [/bada/i],
631
    describe(ua) {
632
      const browser = {
6✔
633
        name: 'Bada',
634
      };
635
      const version = Utils.getFirstMatch(/dolfin\/(\d+(\.?_?\d+)+)/i, ua);
6✔
636

637
      if (version) {
6!
638
        browser.version = version;
6✔
639
      }
640

641
      return browser;
6✔
642
    },
643
  },
644
  {
645
    test: [/tizen/i],
646
    describe(ua) {
647
      const browser = {
10✔
648
        name: 'Tizen',
649
      };
650
      const version = Utils.getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
10✔
651

652
      if (version) {
10!
653
        browser.version = version;
10✔
654
      }
655

656
      return browser;
10✔
657
    },
658
  },
659
  {
660
    test: [/qupzilla/i],
661
    describe(ua) {
662
      const browser = {
4✔
663
        name: 'QupZilla',
664
      };
665
      const version = Utils.getFirstMatch(/(?:qupzilla)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
4!
666

667
      if (version) {
4!
668
        browser.version = version;
4✔
669
      }
670

671
      return browser;
4✔
672
    },
673
  },
674
  {
675
    test: [/firefox|iceweasel|fxios/i],
676
    describe(ua) {
677
      const browser = {
44✔
678
        name: 'Firefox',
679
      };
680
      const version = Utils.getFirstMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i, ua);
44✔
681

682
      if (version) {
44!
683
        browser.version = version;
44✔
684
      }
685

686
      return browser;
44✔
687
    },
688
  },
689
  {
690
    test: [/electron/i],
691
    describe(ua) {
692
      const browser = {
8✔
693
        name: 'Electron',
694
      };
695
      const version = Utils.getFirstMatch(/(?:electron)\/(\d+(\.?_?\d+)+)/i, ua);
8✔
696

697
      if (version) {
8!
698
        browser.version = version;
8✔
699
      }
700

701
      return browser;
8✔
702
    },
703
  },
704
  {
705
    test: [/MiuiBrowser/i],
706
    describe(ua) {
707
      const browser = {
4✔
708
        name: 'Miui',
709
      };
710
      const version = Utils.getFirstMatch(/(?:MiuiBrowser)[\s/](\d+(\.?_?\d+)+)/i, ua);
4✔
711

712
      if (version) {
4✔
713
        browser.version = version;
2✔
714
      }
715

716
      return browser;
4✔
717
    },
718
  },
719
  {
720
    test: [/chromium/i],
721
    describe(ua) {
722
      const browser = {
4✔
723
        name: 'Chromium',
724
      };
725
      const version = Utils.getFirstMatch(/(?:chromium)[\s/](\d+(\.?_?\d+)+)/i, ua) || Utils.getFirstMatch(commonVersionIdentifier, ua);
4!
726

727
      if (version) {
4!
728
        browser.version = version;
4✔
729
      }
730

731
      return browser;
4✔
732
    },
733
  },
734
  {
735
    test: [/chrome|crios|crmo/i],
736
    describe(ua) {
737
      const browser = {
52✔
738
        name: 'Chrome',
739
      };
740
      const version = Utils.getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.?_?\d+)+)/i, ua);
52✔
741

742
      if (version) {
52!
743
        browser.version = version;
52✔
744
      }
745

746
      return browser;
52✔
747
    },
748
  },
749
  {
750
    test: [/GSA/i],
751
    describe(ua) {
752
      const browser = {
2✔
753
        name: 'Google Search',
754
      };
755
      const version = Utils.getFirstMatch(/(?:GSA)\/(\d+(\.?_?\d+)+)/i, ua);
2✔
756

757
      if (version) {
2!
758
        browser.version = version;
2✔
759
      }
760

761
      return browser;
2✔
762
    },
763
  },
764

765
  /* Android Browser */
766
  {
767
    test(parser) {
768
      const notLikeAndroid = !parser.test(/like android/i);
76✔
769
      const butAndroid = parser.test(/android/i);
76✔
770
      return notLikeAndroid && butAndroid;
76✔
771
    },
772
    describe(ua) {
773
      const browser = {
14✔
774
        name: 'Android Browser',
775
      };
776
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua);
14✔
777

778
      if (version) {
14!
779
        browser.version = version;
14✔
780
      }
781

782
      return browser;
14✔
783
    },
784
  },
785

786
  /* PlayStation 4 */
787
  {
788
    test: [/playstation 4/i],
789
    describe(ua) {
790
      const browser = {
2✔
791
        name: 'PlayStation 4',
792
      };
793
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua);
2✔
794

795
      if (version) {
2!
796
        browser.version = version;
×
797
      }
798

799
      return browser;
2✔
800
    },
801
  },
802

803
  /* Safari */
804
  {
805
    test: [/safari|applewebkit/i],
806
    describe(ua) {
807
      const browser = {
54✔
808
        name: 'Safari',
809
      };
810
      const version = Utils.getFirstMatch(commonVersionIdentifier, ua);
54✔
811

812
      if (version) {
54✔
813
        browser.version = version;
48✔
814
      }
815

816
      return browser;
54✔
817
    },
818
  },
819

820
  /* Something else */
821
  {
822
    test: [/.*/i],
823
    describe(ua) {
824
      /* Here we try to make sure that there are explicit details about the device
825
       * in order to decide what regexp exactly we want to apply
826
       * (as there is a specific decision based on that conclusion)
827
       */
828
      const regexpWithoutDeviceSpec = /^(.*)\/(.*) /;
6✔
829
      const regexpWithDeviceSpec = /^(.*)\/(.*)[ \t]\((.*)/;
6✔
830
      const hasDeviceSpec = ua.search('\\(') !== -1;
6✔
831
      const regexp = hasDeviceSpec ? regexpWithDeviceSpec : regexpWithoutDeviceSpec;
6✔
832
      return {
6✔
833
        name: Utils.getFirstMatch(regexp, ua),
834
        version: Utils.getSecondMatch(regexp, ua),
835
      };
836
    },
837
  },
838
];
839

840
export default browsersList;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc