Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
test
autotest-playwright-web-spd
Commits
79a08ff4
Commit
79a08ff4
authored
Sep 18, 2026
by
liguangyu06
Browse files
修复报表表头 strict mode、列设置关面板超时及汇总/排序定位,避免夜间全量被脚本误杀。
parent
35fe36af
Changes
87
Hide whitespace changes
Inline
Side-by-side
pages/spd/center-report-sheet.page.ts
View file @
79a08ff4
...
...
@@ -7,9 +7,10 @@ import { columnHeaderMatchTokens } from '../../core/column-header-match.util';
import
{
CenterReportSheetLocators
}
from
'
../../locators/spd/center-report-sheet.locator
'
;
import
{
DownloadCenterComponent
}
from
'
../components/download-center.component
'
;
import
{
DrawerFormControls
}
from
'
./helpers/drawer-form-controls.helper
'
;
import
{
ColumnSortControls
}
from
'
./helpers/column-sort.helper
'
;
import
{
ColumnSortControls
,
clickColumnSortControl
}
from
'
./helpers/column-sort.helper
'
;
import
{
ColumnSettingsControls
,
executeColumnSettingsToggleRestore
,
findUsableColumnSettingsTrigger
,
revealColumnSettingsTriggerByScroll
,
}
from
'
./helpers/column-settings.helper
'
;
...
...
@@ -66,7 +67,7 @@ export class CenterReportSheetPage extends BasePage {
columnSorter
:
(
columnName
)
=>
this
.
locators
.
columnSorter
(
columnName
),
columnSorterUp
:
(
columnName
)
=>
this
.
locators
.
columnSorterUp
(
columnName
),
columnSorterDown
:
(
columnName
)
=>
this
.
locators
.
columnSorterDown
(
columnName
),
clickColumnSort
:
(
columnName
)
=>
this
.
clickColumnSort
(
columnName
),
clickColumnSort
:
(
columnName
,
order
)
=>
this
.
clickColumnSort
(
columnName
,
order
),
getFirstRowText
:
()
=>
this
.
getFirstRowText
(),
getColumnValues
:
(
columnName
,
maxRows
)
=>
this
.
getColumnValues
(
columnName
,
maxRows
),
getRowSignatures
:
(
maxRows
)
=>
this
.
getRowSignatures
(
maxRows
),
...
...
@@ -85,7 +86,7 @@ export class CenterReportSheetPage extends BasePage {
});
this
.
summaryMatchControls
=
new
ReportSummaryMatchControls
({
profile
:
detailSheetSummaryMatch
,
reportBody
:
this
.
locators
.
report
.
locator
(
'
body
'
)
,
reportBody
:
this
.
locators
.
report
,
dataRows
:
this
.
locators
.
dataRows
,
paginationTotal
:
this
.
locators
.
paginationTotal
,
sampleRowIndex
:
centerReportSheetData
.
sampleRowIndex
,
...
...
@@ -114,8 +115,14 @@ export class CenterReportSheetPage extends BasePage {
.
locator
(
'
iframe[src*="centerPages/sheet"]
'
)
.
first
()
.
waitFor
({
state
:
'
attached
'
,
timeout
:
timeouts
.
pageReady
});
await
this
.
waitVisible
(
this
.
locators
.
searchBtn
,
timeouts
.
pageReady
);
if
(
!
(
await
this
.
locators
.
productName
.
isVisible
().
catch
(()
=>
false
)))
{
const
toggle
=
this
.
locators
.
report
.
getByText
(
/^
(
展开|收起
)
$/
).
first
();
if
(
await
toggle
.
isVisible
().
catch
(()
=>
false
))
{
await
toggle
.
click
().
catch
(()
=>
undefined
);
}
}
await
this
.
waitVisible
(
this
.
locators
.
productName
,
timeouts
.
pageReady
);
await
this
.
waitVisible
(
this
.
locators
.
searchBtn
);
await
this
.
waitVisible
(
this
.
locators
.
advancedFilter
);
await
this
.
assertPageHealthy
(
'
页面加载完成
'
);
},
...
...
@@ -244,8 +251,21 @@ export class CenterReportSheetPage extends BasePage {
}
async
getFirstRowText
():
Promise
<
string
>
{
await
expect
(
this
.
locators
.
dataRows
.
first
()).
toBeVisible
({
timeout
:
15
_000
});
return
(
await
this
.
locators
.
dataRows
.
first
().
innerText
()).
trim
();
await
expect
.
poll
(
async
()
=>
this
.
locators
.
dataRows
.
count
(),
{
timeout
:
20
_000
})
.
toBeGreaterThan
(
0
);
const
rows
=
this
.
locators
.
dataRows
;
const
count
=
await
rows
.
count
();
let
row
=
rows
.
first
();
for
(
let
i
=
0
;
i
<
Math
.
min
(
count
,
20
);
i
+=
1
)
{
const
candidate
=
rows
.
nth
(
i
);
if
(
await
candidate
.
isVisible
().
catch
(()
=>
false
))
{
row
=
candidate
;
break
;
}
}
await
row
.
scrollIntoViewIfNeeded
().
catch
(()
=>
undefined
);
return
((
await
row
.
innerText
().
catch
(()
=>
''
))
??
''
).
trim
();
}
async
getColumnValues
(
columnName
:
string
,
maxRows
=
8
):
Promise
<
string
[]
>
{
...
...
@@ -669,13 +689,25 @@ export class CenterReportSheetPage extends BasePage {
);
}
/** 点击指定列
排序控件
(Ant Design:
无序 → 升序 → 降序循环
)。 */
async
clickColumnSort
(
columnName
:
string
):
Promise
<
void
>
{
/** 点击指定列
升序或降序按钮
(Ant Design:
▲ / ▼ 分别激活
)。 */
async
clickColumnSort
(
columnName
:
string
,
order
?:
'
ascend
'
|
'
descend
'
):
Promise
<
void
>
{
await
this
.
scrollColumnIntoView
(
columnName
);
const
header
=
this
.
locators
.
columnHeaderCell
(
columnName
);
const
sorter
=
this
.
locators
.
columnSorter
(
columnName
);
await
this
.
waitVisible
(
sorter
,
15
_000
);
await
test
.
step
(
`点击
${
columnName
}
列排序`
,
async
()
=>
{
await
sorter
.
click
({
force
:
true
});
if
((
await
sorter
.
count
())
>
0
)
{
await
sorter
.
waitFor
({
state
:
'
attached
'
,
timeout
:
15
_000
}).
catch
(()
=>
undefined
);
}
else
{
await
this
.
waitVisible
(
header
,
15
_000
);
}
const
label
=
order
===
'
descend
'
?
'
降序
'
:
order
===
'
ascend
'
?
'
升序
'
:
'
排序
'
;
await
test
.
step
(
`点击
${
columnName
}
列
${
label
}
`
,
async
()
=>
{
await
clickColumnSortControl
({
header
,
sorter
,
up
:
this
.
locators
.
columnSorterUp
(
columnName
),
down
:
this
.
locators
.
columnSorterDown
(
columnName
),
order
,
});
});
await
this
.
locators
.
report
.
locator
(
'
.ant-spin-spinning
'
).
waitFor
({
state
:
'
hidden
'
,
timeout
:
30
_000
}).
catch
(()
=>
undefined
);
await
this
.
locators
.
tableBody
.
waitFor
({
state
:
'
visible
'
,
timeout
:
15
_000
}).
catch
(()
=>
undefined
);
...
...
@@ -751,16 +783,32 @@ export class CenterReportSheetPage extends BasePage {
await
sorter
.
scrollIntoViewIfNeeded
().
catch
(()
=>
undefined
);
}
/** 当前页面指定列是否可见且带排序控件。 */
/** 当前页面指定列是否可见且带排序控件。
固定列 sorter 可能被 overflow 裁切,不能只信 isVisible。
*/
async
isColumnSortable
(
columnName
:
string
):
Promise
<
boolean
>
{
try
{
await
this
.
scrollColumnIntoView
(
columnName
);
const
sorter
=
this
.
locators
.
columnSorter
(
columnName
);
const
sortable
=
(
await
sorter
.
count
())
>
0
&&
(
await
sorter
.
isVisible
());
if
(
!
sortable
)
{
if
((
await
sorter
.
count
())
>
0
)
{
return
true
;
}
const
header
=
this
.
locators
.
columnHeaderCell
(
columnName
);
if
((
await
header
.
count
())
===
0
)
{
await
this
.
assertPageHealthy
(
`列「
${
columnName
}
」不可排序`
);
return
false
;
}
return
sortable
;
const
hasSorter
=
await
header
.
evaluate
((
el
)
=>
{
const
th
=
((
el
as
HTMLElement
).
closest
(
'
th
'
)
??
el
)
as
HTMLElement
;
return
Boolean
(
th
.
querySelector
(
'
.ant-table-column-sorter
'
)
||
th
.
classList
.
contains
(
'
ant-table-column-has-sorters
'
),
);
})
.
catch
(()
=>
false
);
if
(
!
hasSorter
)
{
await
this
.
assertPageHealthy
(
`列「
${
columnName
}
」不可排序`
);
}
return
hasSorter
;
}
catch
{
await
this
.
assertPageHealthy
(
`列「
${
columnName
}
」不可排序`
);
return
false
;
...
...
@@ -901,26 +949,32 @@ export class CenterReportSheetPage extends BasePage {
}
async
openColumnSettings
():
Promise
<
void
>
{
await
this
.
closeColumnSettingsPanel
();
await
this
.
locators
.
columnSettingsPanel
().
waitFor
({
state
:
'
hidden
'
,
timeout
:
3
_000
}).
catch
(()
=>
undefined
);
await
revealColumnSettingsTriggerByScroll
({
scroll
:
this
.
tableScrollLayoutControls
,
tableWrapper
:
this
.
locators
.
report
.
locator
(
'
.ant-table-wrapper
'
).
first
(),
triggerCandidates
:
[
this
.
locators
.
columnSettingsTrigger
],
horizontalScrollTargetColumn
:
centerReportSheetData
.
horizontalScrollTargetColumn
,
horizontalScrollDragDelta
:
centerReportSheetData
.
horizontalScrollDragDelta
,
scrollColumnIntoView
:
async
(
column
)
=>
{
await
this
.
locators
.
tableHeader
(
column
).
scrollIntoViewIfNeeded
().
catch
(()
=>
undefined
);
},
});
if
(
!
(
await
findUsableColumnSettingsTrigger
([
this
.
locators
.
columnSettingsTrigger
])))
{
const
panel
=
this
.
locators
.
columnSettingsPanel
();
if
(
await
panel
.
isVisible
().
catch
(()
=>
false
))
{
return
;
}
const
triggerCandidates
=
[
this
.
locators
.
columnSettingsTrigger
];
let
trigger
=
await
findUsableColumnSettingsTrigger
(
triggerCandidates
);
if
(
!
trigger
)
{
await
revealColumnSettingsTriggerByScroll
({
scroll
:
this
.
tableScrollLayoutControls
,
tableWrapper
:
this
.
locators
.
report
.
locator
(
'
.ant-table-wrapper
'
).
first
(),
triggerCandidates
,
horizontalScrollTargetColumn
:
centerReportSheetData
.
horizontalScrollTargetColumn
,
horizontalScrollDragDelta
:
centerReportSheetData
.
horizontalScrollDragDelta
,
scrollColumnIntoView
:
async
(
column
)
=>
{
await
this
.
locators
.
tableHeader
(
column
).
scrollIntoViewIfNeeded
().
catch
(()
=>
undefined
);
},
});
trigger
=
await
findUsableColumnSettingsTrigger
(
triggerCandidates
);
}
if
(
!
trigger
)
{
await
this
.
guardedSkip
(
'
列设置齿轮不可见
'
);
}
await
this
.
waitVisible
(
this
.
locators
.
columnSettingsTrigger
,
15
_000
);
await
test
.
step
(
'
打开列设置
'
,
async
()
=>
{
await
t
his
.
locators
.
columnSettingsT
rigger
.
click
({
force
:
true
});
await
trigger
!
.
click
({
force
:
true
});
});
await
this
.
waitVisible
(
this
.
locators
.
columnSettingsP
anel
()
,
10
_000
);
await
this
.
waitVisible
(
p
anel
,
10
_000
);
}
async
expectColumnSettingsPanel
():
Promise
<
void
>
{
...
...
@@ -1002,6 +1056,19 @@ export class CenterReportSheetPage extends BasePage {
return
centerReportSheetData
.
columnSettingsToggleCandidates
[
0
];
}
/** 场景:取消勾选非核心列后表头隐藏,再勾选后表头恢复。 */
async
executeColumnSettingsToggleRestoreTest
():
Promise
<
void
>
{
await
executeColumnSettingsToggleRestore
({
pickToggleColumnName
:
()
=>
this
.
pickToggleColumnName
(),
openColumnSettings
:
()
=>
this
.
openColumnSettings
(),
setColumnVisibleInSettings
:
(
name
,
visible
)
=>
this
.
setColumnVisibleInSettings
(
name
,
visible
),
closeColumnSettingsPanel
:
()
=>
this
.
closeColumnSettingsPanel
(),
expectTableHeaderVisible
:
(
name
,
visible
)
=>
this
.
expectTableHeaderVisible
(
name
,
visible
),
expectSettingsCheckboxChecked
:
(
name
,
checked
)
=>
this
.
expectSettingsCheckboxChecked
(
name
,
checked
),
});
}
/** 选取多个当前可见的非核心列(用于多列调整场景)。 */
async
pickMultipleToggleColumnNames
(
count
=
2
):
Promise
<
string
[]
>
{
const
picked
:
string
[]
=
[];
...
...
pages/spd/consume-trend.page.ts
View file @
79a08ff4
...
...
@@ -162,25 +162,32 @@ export class ConsumeTrendPage extends ReconCostReportSharedPage {
.
locator
(
'
.ant-table-wrapper
'
)
.
nth
(
this
.
data
.
tableWrapperIndex
??
0
);
const
want
=
headerLabel
.
replace
(
/
\s
+/g
,
''
);
const
isShown
=
async
()
=>
wrapper
.
evaluate
((
root
,
label
)
=>
{
const
ths
=
Array
.
from
(
root
.
querySelectorAll
(
'
.ant-table-thead th
'
));
return
ths
.
some
((
th
)
=>
{
const
text
=
(
th
.
textContent
??
''
).
replace
(
/
\s
+/g
,
''
);
if
(
text
!==
label
)
{
return
false
;
}
const
el
=
th
as
HTMLElement
;
return
el
.
offsetWidth
>
0
&&
el
.
offsetHeight
>
0
;
});
},
want
);
if
(
visible
)
{
await
this
.
scrollTableColumnIntoView
(
headerLabel
).
catch
(()
=>
undefined
);
}
await
expect
.
poll
(
async
()
=>
{
if
(
visible
)
{
const
shown
=
await
isShown
();
if
(
visible
&&
!
shown
)
{
await
this
.
scrollTableColumnIntoView
(
headerLabel
).
catch
(()
=>
undefined
);
return
isShown
();
}
return
wrapper
.
evaluate
((
root
,
label
)
=>
{
const
ths
=
Array
.
from
(
root
.
querySelectorAll
(
'
.ant-table-thead th
'
));
return
ths
.
some
((
th
)
=>
{
const
text
=
(
th
.
textContent
??
''
).
replace
(
/
\s
+/g
,
''
);
if
(
text
!==
label
)
{
return
false
;
}
const
el
=
th
as
HTMLElement
;
return
el
.
offsetWidth
>
0
&&
el
.
offsetHeight
>
0
;
});
},
want
);
return
shown
;
},
{
timeout
:
15
_000
},
{
timeout
:
15
_000
,
intervals
:
[
500
,
1
_000
,
2
_000
]
},
)
.
toBe
(
visible
);
}
...
...
pages/spd/cost-dept-classify-charge.page.ts
View file @
79a08ff4
...
...
@@ -2,6 +2,7 @@ import { expect, test, type Page } from '@playwright/test';
import
{
waitAntSpinGone
}
from
'
../../core/smart-wait
'
;
import
{
costDeptClassifyChargeData
}
from
'
../../data/cost-dept-classify-charge.data
'
;
import
{
clickColumnSortControl
,
readColumnSortOrder
}
from
'
./helpers/column-sort.helper
'
;
import
{
ReconCostReportSharedPage
}
from
'
./recon-cost-report-shared.page
'
;
/** 科室成本分类汇总(计费科室):透视表首行表头含可排序「科室名称」。 */
...
...
@@ -42,35 +43,22 @@ export class CostDeptClassifyChargePage extends ReconCostReportSharedPage {
await
expect
(
headerCell
,
`主表未找到可排序列「
${
columnName
}
」`
).
toBeVisible
({
timeout
:
15
_000
});
const
sorter
=
headerCell
.
locator
(
'
.ant-table-column-sorter
'
).
first
();
await
expect
(
sorter
,
`「
${
columnName
}
」列无排序控件`
).
toBeVisible
();
const
clickTarget
=
(
await
headerCell
.
locator
(
'
.ant-table-column-sorters
'
).
count
())
>
0
?
headerCell
.
locator
(
'
.ant-table-column-sorters
'
).
first
()
:
sorter
;
const
readOrder
=
async
():
Promise
<
'
ascend
'
|
'
descend
'
|
null
>
=>
{
const
ariaSort
=
await
headerCell
.
getAttribute
(
'
aria-sort
'
);
if
(
ariaSort
===
'
ascending
'
)
return
'
ascend
'
;
if
(
ariaSort
===
'
descending
'
)
return
'
descend
'
;
const
upActive
=
await
headerCell
.
locator
(
'
.ant-table-column-sorter-up
'
)
.
evaluate
((
el
)
=>
el
.
classList
.
contains
(
'
active
'
)
||
el
.
classList
.
contains
(
'
on
'
))
.
catch
(()
=>
false
);
if
(
upActive
)
return
'
ascend
'
;
const
downActive
=
await
headerCell
.
locator
(
'
.ant-table-column-sorter-down
'
)
.
evaluate
((
el
)
=>
el
.
classList
.
contains
(
'
active
'
)
||
el
.
classList
.
contains
(
'
on
'
))
.
catch
(()
=>
false
);
if
(
downActive
)
return
'
descend
'
;
return
null
;
};
const
up
=
headerCell
.
locator
(
'
.ant-table-column-sorter-up
'
).
first
();
const
down
=
headerCell
.
locator
(
'
.ant-table-column-sorter-down
'
).
first
();
const
clickUntil
=
async
(
target
:
'
ascend
'
|
'
descend
'
):
Promise
<
void
>
=>
{
for
(
let
attempt
=
0
;
attempt
<
4
;
attempt
+=
1
)
{
if
((
await
readOrder
())
===
target
)
return
;
await
clickTarget
.
click
({
force
:
true
});
if
((
await
readColumnSortOrder
(
headerCell
))
===
target
)
return
;
await
clickColumnSortControl
({
header
:
headerCell
,
sorter
,
up
,
down
,
order
:
target
,
});
await
waitAntSpinGone
(
wrapper
);
}
expect
(
await
read
Order
(
),
`列「
${
columnName
}
」未能切换到
${
target
===
'
ascend
'
?
'
升序
'
:
'
降序
'
}
`
).
toBe
(
expect
(
await
read
ColumnSortOrder
(
headerCell
),
`列「
${
columnName
}
」未能切换到
${
target
===
'
ascend
'
?
'
升序
'
:
'
降序
'
}
`
).
toBe
(
target
,
);
};
...
...
pages/spd/cost-finance-psi.page.ts
View file @
79a08ff4
...
...
@@ -5,7 +5,7 @@ import {
costFinancePsiData
,
costFinancePsiSummaryMetrics
,
}
from
'
../../data/cost-finance-psi.data
'
;
import
{
parse
ReportNumber
}
from
'
./helpers/report-summary.helper
'
;
import
{
parse
SummaryMetricFromText
,
readReportFooterText
}
from
'
./helpers/report-summary.helper
'
;
import
{
ReconCostReportSharedPage
}
from
'
./recon-cost-report-shared.page
'
;
export
class
CostFinancePsiPage
extends
ReconCostReportSharedPage
{
...
...
@@ -67,32 +67,14 @@ export class CostFinancePsiPage extends ReconCostReportSharedPage {
private
async
readSummaryMetric
(
labels
:
readonly
string
[]):
Promise
<
number
>
{
const
footerText
=
await
this
.
readFooterText
();
for
(
const
label
of
labels
)
{
const
fromFooter
=
this
.
parseFooterMetric
(
footerText
,
label
);
const
fromFooter
=
parseSummaryMetricFromText
(
footerText
,
label
);
if
(
Number
.
isFinite
(
fromFooter
))
return
fromFooter
;
}
return
NaN
;
}
private
parseFooterMetric
(
bodyText
:
string
,
label
:
string
):
number
{
const
compact
=
bodyText
.
replace
(
/
\s
+/g
,
'
'
);
const
escaped
=
label
.
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
'
\\
$&
'
);
const
matched
=
compact
.
match
(
new
RegExp
(
`
${
escaped
}
\\s*[=::]?\\s*([+\\-−–]?[\\d,]+(?:\\.[\\d]+)?|—|-)`
));
if
(
!
matched
)
{
return
NaN
;
}
return
parseReportNumber
(
matched
[
1
]);
}
private
async
readFooterText
():
Promise
<
string
>
{
const
chips
=
this
.
locators
.
report
.
locator
(
'
span.margin-left-8, .summary-footer, .report-summary, .ant-table-summary
'
)
.
filter
({
hasText
:
/期初|入库|出库|报损|期末/
});
const
parts
=
await
chips
.
allInnerTexts
();
const
joined
=
parts
.
map
((
text
)
=>
text
.
replace
(
/
\s
+/g
,
'
'
).
trim
()).
filter
(
Boolean
).
join
(
'
'
);
if
(
/期初|入库|出库|报损|期末/
.
test
(
joined
))
{
return
joined
;
}
return
(
await
this
.
locators
.
report
.
locator
(
'
body
'
).
innerText
()).
replace
(
/
\s
+/g
,
'
'
);
return
readReportFooterText
(
this
.
locators
.
report
,
[
'
期初
'
,
'
入库
'
,
'
出库
'
,
'
报损
'
,
'
期末
'
,
'
总金额
'
]);
}
}
...
...
pages/spd/cost-product-rank.page.ts
View file @
79a08ff4
...
...
@@ -204,6 +204,77 @@ export class CostProductRankPage extends ReconCostReportSharedPage {
.
getByText
(
name
,
{
exact
:
false
});
}
/** 左侧分页总条数(勿读右侧明细分页)。 */
async
expectLeftPaginationTotal
():
Promise
<
void
>
{
await
this
.
withPaginationTableIndex
(
this
.
data
.
tableWrapperIndex
??
0
,
()
=>
this
.
expectPaginationTotal
(),
);
}
/**
* STAT-CPR-022:左右分页独立。
* 先切左侧第 2 页并确认右侧页码不变,再在右侧有 2 页时切右侧第 2 页并确认左侧仍停在第 2 页。
*/
async
executeIndependentDualTablePage2Test
():
Promise
<
void
>
{
const
leftIdx
=
this
.
data
.
tableWrapperIndex
??
0
;
const
rightIdx
=
this
.
data
.
detailTableWrapperIndex
??
1
;
const
rightPageBefore
=
await
this
.
readActivePageForTable
(
rightIdx
);
await
this
.
executePage2SwitchTest
(
leftIdx
);
await
this
.
expectActivePageForTable
(
leftIdx
,
2
);
if
(
rightPageBefore
)
{
const
rightPageNum
=
Number
.
parseInt
(
rightPageBefore
,
10
);
if
(
Number
.
isFinite
(
rightPageNum
))
{
await
this
.
expectActivePageForTable
(
rightIdx
,
rightPageNum
);
}
}
await
this
.
selectProductRankRow
();
await
expect
(
this
.
locators
.
detailDataRows
.
first
()).
toBeVisible
({
timeout
:
15
_000
});
const
rightHasPage2
=
await
this
.
withPaginationTableIndex
(
rightIdx
,
async
()
=>
(
await
this
.
locators
.
paginationItem
(
2
).
count
())
>
0
,
);
if
(
!
rightHasPage2
)
{
return
;
}
const
leftFirst
=
await
this
.
getFirstRowText
();
await
this
.
withPaginationTableIndex
(
rightIdx
,
async
()
=>
{
const
first
=
await
this
.
getFirstRowText
();
const
item
=
this
.
locators
.
paginationItem
(
2
);
await
this
.
locators
.
paginationTotal
.
scrollIntoViewIfNeeded
().
catch
(()
=>
undefined
);
await
item
.
evaluate
((
node
)
=>
{
const
link
=
node
.
querySelector
(
'
a
'
)
as
HTMLElement
|
null
;
(
link
??
(
node
as
HTMLElement
)).
click
();
});
await
this
.
locators
.
report
.
locator
(
'
.ant-spin-spinning
'
)
.
waitFor
({
state
:
'
hidden
'
,
timeout
:
30
_000
})
.
catch
(()
=>
undefined
);
await
this
.
waitForTableReady
();
await
this
.
expectActivePage
(
2
);
const
second
=
await
this
.
getFirstRowText
();
expect
(
first
,
'
右侧第1页首行应有数据
'
).
toBeTruthy
();
expect
(
second
,
'
右侧第2页首行应有数据
'
).
toBeTruthy
();
expect
(
second
).
not
.
toEqual
(
first
);
});
await
this
.
expectActivePageForTable
(
leftIdx
,
2
);
const
leftAfterRightPage2
=
await
this
.
getFirstRowText
();
expect
(
leftAfterRightPage2
,
'
右侧翻页不应改写左侧当前页数据
'
).
toEqual
(
leftFirst
);
}
private
async
readActivePageForTable
(
tableWrapperIndex
:
number
):
Promise
<
string
>
{
return
this
.
withPaginationTableIndex
(
tableWrapperIndex
,
async
()
=>
{
const
active
=
this
.
locators
.
paginationActiveItem
();
if
((
await
active
.
count
())
===
0
)
return
''
;
return
((
await
active
.
innerText
().
catch
(()
=>
''
))
??
''
).
trim
();
});
}
private
async
expectActivePageForTable
(
tableWrapperIndex
:
number
,
pageNum
:
number
):
Promise
<
void
>
{
await
this
.
withPaginationTableIndex
(
tableWrapperIndex
,
()
=>
this
.
expectActivePage
(
pageNum
));
}
/** 左侧产品排名列表:查询后导出。 */
async
exportLeftRankList
():
Promise
<
void
>
{
await
this
.
search
();
...
...
pages/spd/helpers/column-resize.helper.ts
View file @
79a08ff4
...
...
@@ -46,8 +46,8 @@ export class ColumnResizeControls {
const
tokens
=
columnHeaderMatchTokens
(
columnName
);
return
this
.
tableWrapper
().
evaluate
(
(
root
,
payload
)
=>
{
// 与 sortColumnHeaderAt / visibleThead 同一套 sticky 表头,禁止用 fixed 克隆的另一套列下标。
const
theadRoots
=
[
root
.
querySelector
(
'
.ant-table-fixed-left .ant-table-thead
'
),
root
.
querySelector
(
'
.ant-table-container > .ant-table-header .ant-table-thead
'
),
root
.
querySelector
(
'
.ant-table-header .ant-table-thead
'
),
root
.
querySelector
(
'
.ant-table-content .ant-table-thead
'
),
...
...
@@ -64,8 +64,10 @@ export class ColumnResizeControls {
return
headers
as
HTMLElement
[];
};
const
headerGroups
=
theadRoots
.
map
(
collectHeaders
).
filter
((
group
)
=>
group
.
length
>
0
);
if
(
headerGroups
.
length
===
0
)
return
-
1
;
const
headerGroups
=
theadRoots
.
slice
(
0
,
1
).
map
(
collectHeaders
).
filter
((
group
)
=>
group
.
length
>
0
);
const
fallbackGroups
=
theadRoots
.
map
(
collectHeaders
).
filter
((
group
)
=>
group
.
length
>
0
);
const
searchGroups
=
headerGroups
.
length
>
0
?
headerGroups
:
fallbackGroups
;
if
(
searchGroups
.
length
===
0
)
return
-
1
;
const
hasSorter
=
(
th
:
HTMLElement
)
=>
{
const
text
=
(
th
.
textContent
??
''
).
replace
(
/
\s
+/g
,
'
'
);
...
...
@@ -79,8 +81,8 @@ export class ColumnResizeControls {
let
bestIndex
=
-
1
;
let
bestScore
=
0
;
let
bestGroup
=
h
ea
der
Groups
[
0
]
!
;
for
(
const
headers
of
h
ea
der
Groups
)
{
let
bestGroup
=
s
ea
rch
Groups
[
0
]
!
;
for
(
const
headers
of
s
ea
rch
Groups
)
{
for
(
const
[
i
,
th
]
of
headers
.
entries
())
{
const
clone
=
th
.
cloneNode
(
true
)
as
HTMLElement
;
clone
...
...
pages/spd/helpers/column-settings.helper.ts
View file @
79a08ff4
import
{
expect
,
test
,
type
Locator
}
from
'
@playwright/test
'
;
import
{
test
,
type
Locator
}
from
'
@playwright/test
'
;
import
type
{
TableScrollLayoutControls
}
from
'
./table-scroll-layout.helper
'
;
...
...
@@ -141,25 +141,55 @@ export class ColumnSettingsControls {
return
;
}
for
(
let
attempt
=
0
;
attempt
<
6
;
attempt
+=
1
)
{
// 先 Escape:点齿轮在宽表上常被遮挡,默认 30s click 会把 180s 用例吃满
await
this
.
ctx
.
reportBody
.
press
(
'
Escape
'
).
catch
(()
=>
undefined
);
for
(
let
attempt
=
0
;
attempt
<
4
;
attempt
+=
1
)
{
if
(
!
(
await
this
.
isPanelVisible
()))
{
break
;
}
await
this
.
toggleTrigger
(
1
);
await
panel
.
waitFor
({
state
:
'
hidden
'
,
timeout
:
2
_000
}).
catch
(()
=>
undefined
);
await
panel
.
waitFor
({
state
:
'
hidden
'
,
timeout
:
1
_000
}).
catch
(()
=>
undefined
);
}
if
(
await
this
.
isPanelVisible
())
{
await
this
.
ctx
.
reportBody
.
press
(
'
Escape
'
).
catch
(()
=>
undefined
);
// 点外侧关闭,禁止点「查询」以免整表重载把恢复用例拖超时
await
this
.
ctx
.
clickOutsideTarget
.
click
({
force
:
true
,
timeout
:
3
_000
}).
catch
(()
=>
undefined
);
await
this
.
toggleTrigger
(
1
);
}
if
(
await
this
.
isPanelVisible
())
{
await
expect
(
panel
).
toBeHidden
({
timeout
:
10
_000
}).
catch
(
async
()
=>
{
await
this
.
toggleTrigger
(
2
);
});
await
panel
.
waitFor
({
state
:
'
hidden
'
,
timeout
:
2
_000
}).
catch
(()
=>
undefined
);
}
});
}
}
export
type
ColumnSettingsToggleRestoreActions
=
{
pickToggleColumnName
:
()
=>
Promise
<
string
>
;
openColumnSettings
:
()
=>
Promise
<
void
>
;
setColumnVisibleInSettings
:
(
columnName
:
string
,
visible
:
boolean
)
=>
Promise
<
void
>
;
closeColumnSettingsPanel
:
()
=>
Promise
<
void
>
;
expectTableHeaderVisible
:
(
columnName
:
string
,
visible
:
boolean
)
=>
Promise
<
void
>
;
expectSettingsCheckboxChecked
:
(
label
:
string
,
checked
:
boolean
)
=>
Promise
<
void
>
;
};
/**
* 列设置取消勾选后再勾选列表列恢复:2 次打开面板即可。
* 不再第三次开面板验勾选,避免宽表反复横滚把 120s 用例吃满。
*/
export
async
function
executeColumnSettingsToggleRestore
(
actions
:
ColumnSettingsToggleRestoreActions
,
):
Promise
<
void
>
{
test
.
setTimeout
(
180
_000
);
await
test
.
step
(
'
列设置取消勾选后再勾选列表列恢复
'
,
async
()
=>
{
const
columnName
=
await
actions
.
pickToggleColumnName
();
await
actions
.
openColumnSettings
();
await
actions
.
setColumnVisibleInSettings
(
columnName
,
false
);
await
actions
.
closeColumnSettingsPanel
();
await
actions
.
expectTableHeaderVisible
(
columnName
,
false
);
await
actions
.
openColumnSettings
();
await
actions
.
expectSettingsCheckboxChecked
(
columnName
,
false
);
await
actions
.
setColumnVisibleInSettings
(
columnName
,
true
);
await
actions
.
closeColumnSettingsPanel
();
await
actions
.
expectTableHeaderVisible
(
columnName
,
true
);
});
}
pages/spd/helpers/column-sort.helper.ts
View file @
79a08ff4
...
...
@@ -7,7 +7,7 @@ export type ColumnSortControlsContext = {
columnSorter
:
(
columnName
:
string
)
=>
Locator
;
columnSorterUp
:
(
columnName
:
string
)
=>
Locator
;
columnSorterDown
:
(
columnName
:
string
)
=>
Locator
;
clickColumnSort
:
(
columnName
:
string
)
=>
Promise
<
void
>
;
clickColumnSort
:
(
columnName
:
string
,
order
?:
'
ascend
'
|
'
descend
'
)
=>
Promise
<
void
>
;
getFirstRowText
?:
()
=>
Promise
<
string
>
;
getColumnValues
?:
(
columnName
:
string
,
maxRows
?:
number
)
=>
Promise
<
string
[]
>
;
getRowSignatures
?:
(
maxRows
?:
number
)
=>
Promise
<
string
[]
>
;
...
...
@@ -49,236 +49,140 @@ export function isColumnValuesSorted(values: string[], order: 'ascend' | 'descen
return
true
;
}
/** 表格列排序:升序 → 降序完整切换校验。 */
export
class
ColumnSortControls
{
constructor
(
private
readonly
ctx
:
ColumnSortControlsContext
)
{}
async
getColumnSortOrder
(
columnName
:
string
):
Promise
<
ColumnSortOrder
>
{
const
header
=
this
.
ctx
.
columnHeaderCell
(
columnName
);
const
ariaSort
=
await
header
.
getAttribute
(
'
aria-sort
'
);
if
(
ariaSort
===
'
ascending
'
)
{
return
'
ascend
'
;
}
if
(
ariaSort
===
'
descending
'
)
{
return
'
descend
'
;
}
const
upActive
=
await
this
.
ctx
.
columnSorterUp
(
columnName
)
.
evaluate
((
el
)
=>
el
.
classList
.
contains
(
'
active
'
)
||
el
.
classList
.
contains
(
'
on
'
))
.
catch
(()
=>
false
);
if
(
upActive
)
{
return
'
ascend
'
;
}
const
downActive
=
await
this
.
ctx
.
columnSorterDown
(
columnName
)
.
evaluate
((
el
)
=>
el
.
classList
.
contains
(
'
active
'
)
||
el
.
classList
.
contains
(
'
on
'
))
.
catch
(()
=>
false
);
if
(
downActive
)
{
return
'
descend
'
;
}
const
headerSorted
=
await
header
.
evaluate
((
el
)
=>
{
const
th
=
el
.
closest
(
'
th
'
)
??
el
;
return
(
th
.
classList
.
contains
(
'
ant-table-column-sort
'
)
||
th
.
getAttribute
(
'
aria-sort
'
)
===
'
ascending
'
||
th
.
getAttribute
(
'
aria-sort
'
)
===
'
descending
'
);
})
.
catch
(()
=>
false
);
if
(
headerSorted
)
{
return
'
ascend
'
;
}
/**
* 读取表头当前排序方向。
* 升序/降序是两枚独立按钮,必须看对应箭头的激活态,不能把「已排序」一律当成升序。
*/
export
async
function
readColumnSortOrder
(
header
:
Locator
):
Promise
<
ColumnSortOrder
>
{
return
header
.
evaluate
((
el
)
=>
{
const
th
=
((
el
as
HTMLElement
).
closest
(
'
th
'
)
??
el
)
as
HTMLElement
;
const
aria
=
th
.
getAttribute
(
'
aria-sort
'
);
if
(
aria
===
'
ascending
'
)
return
'
ascend
'
;
if
(
aria
===
'
descending
'
)
return
'
descend
'
;
const
up
=
th
.
querySelector
(
'
.ant-table-column-sorter-up, .anticon-caret-up
'
,
)
as
HTMLElement
|
null
;
const
down
=
th
.
querySelector
(
'
.ant-table-column-sorter-down, .anticon-caret-down
'
,
)
as
HTMLElement
|
null
;
const
classState
=
(
node
:
HTMLElement
|
null
):
'
on
'
|
'
off
'
|
null
=>
{
if
(
!
node
)
return
null
;
const
off
=
node
.
classList
.
contains
(
'
off
'
);
const
on
=
node
.
classList
.
contains
(
'
active
'
)
||
node
.
classList
.
contains
(
'
on
'
);
if
(
on
&&
!
off
)
return
'
on
'
;
if
(
off
&&
!
on
)
return
'
off
'
;
return
null
;
};
const
upClass
=
classState
(
up
);
const
downClass
=
classState
(
down
);
if
(
upClass
===
'
on
'
&&
downClass
!==
'
on
'
)
return
'
ascend
'
;
if
(
downClass
===
'
on
'
&&
upClass
!==
'
on
'
)
return
'
descend
'
;
const
visualScore
=
(
node
:
HTMLElement
|
null
):
number
=>
{
if
(
!
node
)
return
0
;
const
paintTarget
=
(
node
.
querySelector
(
'
svg, path, i, em
'
)
as
HTMLElement
|
null
)
??
node
;
const
style
=
getComputedStyle
(
paintTarget
);
const
parentStyle
=
getComputedStyle
(
node
);
const
opacity
=
Number
.
parseFloat
(
style
.
opacity
||
parentStyle
.
opacity
||
'
1
'
);
const
colorSource
=
(
style
.
fill
&&
style
.
fill
!==
'
none
'
&&
style
.
fill
!==
'
currentColor
'
?
style
.
fill
:
''
)
||
style
.
color
||
parentStyle
.
color
;
const
match
=
colorSource
.
match
(
/
(\d
+
)\s
*,
\s
*
(\d
+
)\s
*,
\s
*
(\d
+
)
/
);
if
(
!
match
)
return
opacity
;
const
r
=
Number
(
match
[
1
]);
const
g
=
Number
(
match
[
2
]);
const
b
=
Number
(
match
[
3
]);
const
max
=
Math
.
max
(
r
,
g
,
b
);
const
min
=
Math
.
min
(
r
,
g
,
b
);
const
saturation
=
max
===
0
?
0
:
(
max
-
min
)
/
max
;
return
opacity
*
(
0.25
+
saturation
*
0.75
)
*
(
max
/
255
);
};
const
upScore
=
visualScore
(
up
);
const
downScore
=
visualScore
(
down
);
if
(
upScore
>
downScore
+
0.06
)
return
'
ascend
'
;
if
(
downScore
>
upScore
+
0.06
)
return
'
descend
'
;
return
'
none
'
;
})
.
catch
(()
=>
'
none
'
as
const
);
}
return
'
none
'
;
/** 优先点升序/降序箭头,避免点 sorter 中心时总打到同一枚按钮。 */
export
async
function
clickColumnSortControl
(
options
:
{
header
:
Locator
;
sorter
:
Locator
;
up
:
Locator
;
down
:
Locator
;
order
?:
'
ascend
'
|
'
descend
'
;
}):
Promise
<
void
>
{
const
{
header
,
sorter
,
up
,
down
,
order
}
=
options
;
const
indicator
=
order
===
'
ascend
'
?
up
:
order
===
'
descend
'
?
down
:
null
;
if
(
indicator
&&
(
await
indicator
.
count
().
catch
(()
=>
0
))
>
0
)
{
await
indicator
.
click
({
force
:
true
});
return
;
}
private
async
hasSortedColumnValues
(
columnName
:
string
,
order
:
'
ascend
'
|
'
descend
'
):
Promise
<
boolean
>
{
if
(
!
this
.
ctx
.
getColumnValues
)
{
return
false
;
}
const
values
=
await
this
.
ctx
.
getColumnValues
(
columnName
,
8
).
catch
(()
=>
[]);
return
values
.
length
>=
2
&&
isColumnValuesSorted
(
values
,
order
);
if
((
await
sorter
.
count
().
catch
(()
=>
0
))
>
0
)
{
await
sorter
.
click
({
force
:
true
});
return
;
}
await
header
.
click
({
force
:
true
});
}
private
async
hasSortEffect
(
columnName
:
string
,
baselineSignatures
:
string
[]):
Promise
<
boolean
>
{
if
(
!
this
.
ctx
.
getColumnValues
)
{
// fall through
}
else
{
const
values
=
await
this
.
ctx
.
getColumnValues
(
columnName
,
8
).
catch
(()
=>
[]);
const
unique
=
new
Set
(
values
.
map
((
value
)
=>
value
.
replace
(
/
\s
+/g
,
''
).
trim
())
.
filter
((
value
)
=>
value
&&
value
!==
'
--
'
&&
value
!==
'
-
'
),
);
// 列值全相同:排序后行序可能不变,只要进入排序态即可
if
(
values
.
length
>=
1
&&
unique
.
size
<=
1
&&
(
await
this
.
getColumnSortOrder
(
columnName
))
!==
'
none
'
)
{
return
true
;
}
}
if
(
await
this
.
hasSortedColumnValues
(
columnName
,
'
ascend
'
))
{
return
true
;
}
if
(
await
this
.
hasSortedColumnValues
(
columnName
,
'
descend
'
))
{
return
true
;
}
if
((
await
this
.
getColumnSortOrder
(
columnName
))
!==
'
none
'
)
{
return
true
;
}
if
(
this
.
ctx
.
getRowSignatures
)
{
const
next
=
await
this
.
ctx
.
getRowSignatures
(
8
).
catch
(()
=>
[]);
if
(
next
.
length
>=
2
&&
JSON
.
stringify
(
next
)
!==
JSON
.
stringify
(
baselineSignatures
))
{
return
true
;
}
}
if
(
this
.
ctx
.
getFirstRowText
)
{
const
rowText
=
await
this
.
ctx
.
getFirstRowText
().
catch
(()
=>
''
);
if
(
rowText
&&
baselineSignatures
.
length
>
0
&&
rowText
!==
baselineSignatures
[
0
])
{
return
true
;
}
}
return
false
;
/** 表格列排序:升序按钮 → 降序按钮完整切换校验。 */
export
class
ColumnSortControls
{
constructor
(
private
readonly
ctx
:
ColumnSortControlsContext
)
{}
async
getColumnSortOrder
(
columnName
:
string
):
Promise
<
ColumnSortOrder
>
{
return
readColumnSortOrder
(
this
.
ctx
.
columnHeaderCell
(
columnName
));
}
async
expectColumnSortOrder
(
columnName
:
string
,
order
:
'
ascend
'
|
'
descend
'
):
Promise
<
void
>
{
const
label
=
order
===
'
ascend
'
?
'
升序
'
:
'
降序
'
;
const
header
=
this
.
ctx
.
columnHeaderCell
(
columnName
);
const
indicator
=
order
===
'
ascend
'
?
this
.
ctx
.
columnSorterUp
(
columnName
)
:
this
.
ctx
.
columnSorterDown
(
columnName
);
const
ariaExpected
=
order
===
'
ascend
'
?
'
ascending
'
:
'
descending
'
;
await
test
.
step
(
`校验
${
columnName
}
列
${
label
}
`
,
async
()
=>
{
await
expect
.
poll
(
async
()
=>
{
const
ariaSort
=
await
header
.
getAttribute
(
'
aria-sort
'
);
if
(
ariaSort
===
ariaExpected
)
{
return
true
;
}
const
sortedClass
=
await
header
.
evaluate
((
el
)
=>
{
const
th
=
el
.
closest
(
'
th
'
)
??
el
;
return
th
.
classList
.
contains
(
'
ant-table-column-sort
'
);
})
.
catch
(()
=>
false
);
if
(
sortedClass
)
{
return
true
;
}
const
indicatorActive
=
await
indicator
.
evaluate
((
el
)
=>
el
.
classList
.
contains
(
'
active
'
)
||
el
.
classList
.
contains
(
'
on
'
))
.
catch
(()
=>
false
);
if
(
indicatorActive
)
{
return
true
;
}
return
this
.
hasSortedColumnValues
(
columnName
,
order
);
},
{
timeout
:
15
_000
})
.
toBeTruthy
();
.
poll
(
async
()
=>
this
.
getColumnSortOrder
(
columnName
),
{
timeout
:
15
_000
})
.
toBe
(
order
);
});
}
async
ensureColumnSortOrder
(
columnName
:
string
,
order
:
'
ascend
'
|
'
descend
'
):
Promise
<
void
>
{
let
lastRowText
=
this
.
ctx
.
getFirstRowText
?
await
this
.
ctx
.
getFirstRowText
().
catch
(()
=>
''
)
:
''
;
let
lastColumnValues
=
this
.
ctx
.
getColumnValues
?
await
this
.
ctx
.
getColumnValues
(
columnName
,
8
).
catch
(()
=>
[])
:
[];
let
lastRowSignatures
=
this
.
ctx
.
getRowSignatures
?
await
this
.
ctx
.
getRowSignatures
(
8
).
catch
(()
=>
[])
:
[];
for
(
let
attempt
=
0
;
attempt
<
4
;
attempt
+=
1
)
{
if
((
await
this
.
getColumnSortOrder
(
columnName
))
===
order
)
{
await
this
.
expectColumnSortOrder
(
columnName
,
order
);
return
;
}
if
(
await
this
.
hasSortedColumnValues
(
columnName
,
order
))
{
return
;
}
await
this
.
ctx
.
clickColumnSort
(
columnName
);
if
(
this
.
ctx
.
getRowSignatures
)
{
const
nextSignatures
=
await
this
.
ctx
.
getRowSignatures
(
8
).
catch
(()
=>
[]);
if
(
nextSignatures
.
length
>=
2
&&
JSON
.
stringify
(
nextSignatures
)
!==
JSON
.
stringify
(
lastRowSignatures
)
)
{
if
(
await
this
.
hasSortedColumnValues
(
columnName
,
order
))
{
return
;
}
lastRowSignatures
=
nextSignatures
;
}
}
if
(
this
.
ctx
.
getColumnValues
)
{
const
nextColumnValues
=
await
this
.
ctx
.
getColumnValues
(
columnName
,
8
).
catch
(()
=>
[]);
if
(
nextColumnValues
.
length
>=
2
&&
(
isColumnValuesSorted
(
nextColumnValues
,
order
)
||
JSON
.
stringify
(
nextColumnValues
)
!==
JSON
.
stringify
(
lastColumnValues
))
)
{
if
(
isColumnValuesSorted
(
nextColumnValues
,
order
))
{
return
;
}
lastColumnValues
=
nextColumnValues
;
}
}
if
(
this
.
ctx
.
getFirstRowText
)
{
const
nextRowText
=
await
this
.
ctx
.
getFirstRowText
().
catch
(()
=>
''
);
if
(
nextRowText
&&
nextRowText
!==
lastRowText
)
{
if
(
await
this
.
hasSortedColumnValues
(
columnName
,
order
))
{
return
;
}
lastRowText
=
nextRowText
;
}
}
await
this
.
ctx
.
clickColumnSort
(
columnName
,
order
);
}
await
this
.
expectColumnSortOrder
(
columnName
,
order
);
}
/** 先
校验升序,再点击切换
并校验
降序
。 */
/** 先
点升序箭头并校验,再点降序箭头
并校验。 */
async
expectColumnSortToggle
(
columnName
:
string
):
Promise
<
void
>
{
const
sorter
=
this
.
ctx
.
columnSorter
(
columnName
);
const
header
=
this
.
ctx
.
columnHeaderCell
(
columnName
);
const
sorter
Visible
=
await
sorter
.
isVisible
().
catch
(()
=>
false
);
if
(
!
sorter
Visible
)
{
await
expect
(
header
).
toBeVisible
({
timeout
:
15
_000
}
);
const
sorter
Count
=
await
sorter
.
count
().
catch
(()
=>
0
);
if
(
sorter
Count
>
0
)
{
await
sorter
.
first
().
waitFor
({
state
:
'
attached
'
,
timeout
:
15
_000
}).
catch
(()
=>
undefined
);
}
else
{
await
expect
(
sorter
).
toBeVisible
({
timeout
:
15
_000
});
await
header
.
first
().
waitFor
({
state
:
'
attached
'
,
timeout
:
15
_000
});
}
const
readSignatures
=
async
():
Promise
<
string
[]
>
=>
this
.
ctx
.
getRowSignatures
?
this
.
ctx
.
getRowSignatures
(
8
).
catch
(()
=>
[])
:
[];
const
clickUntilEffect
=
async
(
baseline
:
string
[]):
Promise
<
boolean
>
=>
{
for
(
let
attempt
=
0
;
attempt
<
3
;
attempt
+=
1
)
{
if
(
await
this
.
hasSortEffect
(
columnName
,
baseline
))
{
return
true
;
}
await
this
.
ctx
.
clickColumnSort
(
columnName
);
if
(
await
this
.
hasSortEffect
(
columnName
,
baseline
))
{
return
true
;
}
}
return
false
;
};
const
before
=
await
readSignatures
();
const
firstEffect
=
await
clickUntilEffect
(
before
);
expect
(
firstEffect
,
`
${
columnName
}
列点击后列表顺序未变化或未进入排序状态`
,
).
toBeTruthy
();
await
test
.
step
(
`第一次点击
${
columnName
}
列升序按钮并校验`
,
async
()
=>
{
await
this
.
ensureColumnSortOrder
(
columnName
,
'
ascend
'
);
});
const
midOrder
=
await
this
.
getColumnSortOrder
(
columnName
);
const
mid
=
await
readSignatures
();
const
secondEffect
=
await
clickUntilEffect
(
mid
);
expect
(
secondEffect
,
`
${
columnName
}
列未能切换第二档排序(降序)`
,
).
toBeTruthy
();
expect
(
midOrder
,
`
${
columnName
}
列升序指示器未激活`
).
toBe
(
'
ascend
'
);
await
test
.
step
(
`第二次点击
${
columnName
}
列降序按钮并校验`
,
async
()
=>
{
await
this
.
ensureColumnSortOrder
(
columnName
,
'
descend
'
);
});
const
finalOrder
=
await
this
.
getColumnSortOrder
(
columnName
);
if
(
midOrder
!==
'
none
'
&&
finalOrder
!==
'
none
'
&&
finalOrder
===
midOrder
)
{
expect
(
finalOrder
,
`
${
columnName
}
列降序指示器未与升序区分`
).
not
.
toBe
(
midOrder
);
}
expect
(
finalOrder
,
`
${
columnName
}
列降序指示器未与升序区分`
).
toBe
(
'
descend
'
);
}
}
pages/spd/helpers/export-report.helper.ts
View file @
79a08ff4
...
...
@@ -40,15 +40,19 @@ export async function executeExportReport(options: ExportReportOptions): Promise
await
options
.
page
.
keyboard
.
press
(
'
Escape
'
).
catch
(()
=>
undefined
);
const
exportResponsePromise
=
options
.
page
.
waitForResponse
(
isExportPostResponse
,
{
timeout
:
timeouts
.
exportResponse
,
});
const
waitMs
=
Math
.
max
(
timeouts
.
exportResponse
,
60
_000
);
const
exportResponsePromise
=
options
.
page
.
waitForResponse
(
isExportPostResponse
,
{
timeout
:
waitMs
})
.
catch
(()
=>
null
);
const
downloadPromise
=
options
.
page
.
waitForEvent
(
'
download
'
,
{
timeout
:
waitMs
}).
catch
(()
=>
null
);
await
options
.
clickExport
();
const
response
=
await
exportResponsePromise
;
expect
(
response
,
'
导出接口未在超时内响应
'
).
toBeTruthy
();
expect
(
response
.
ok
(),
`导出接口失败:
${
response
.
status
()}
`
).
toBeTruthy
();
const
[
response
,
download
]
=
await
Promise
.
all
([
exportResponsePromise
,
downloadPromise
]);
expect
(
response
||
download
,
'
导出接口未在超时内响应,且未触发浏览器下载
'
).
toBeTruthy
();
if
(
response
)
{
expect
(
response
.
ok
(),
`导出接口失败:
${
response
.
status
()}
`
).
toBeTruthy
();
}
if
(
options
.
hasVisibleErrorNotice
)
{
expect
(
await
options
.
hasVisibleErrorNotice
(),
'
导出后页面出现错误提示
'
).
toBeFalsy
();
...
...
pages/spd/helpers/report-summary-match.helper.ts
View file @
79a08ff4
import
{
expect
,
test
,
type
Locator
}
from
'
@playwright/test
'
;
import
{
expect
,
test
,
type
FrameLocator
,
type
Locator
}
from
'
@playwright/test
'
;
import
type
{
SummaryMatchProfile
}
from
'
../../../data/center-report-shared.types
'
;
import
{
expectNumbersClose
,
parseReportNumber
}
from
'
./report-summary.helper
'
;
import
{
expectNumbersClose
,
parseReportNumber
,
parseSummaryMetricFromText
,
readReportFooterText
,
}
from
'
./report-summary.helper
'
;
export
type
SummaryMatchSampleRow
=
Record
<
string
,
string
|
undefined
>
;
...
...
@@ -15,28 +20,15 @@ export type RowAggregation = {
export
type
ParsedSummary
=
Record
<
string
,
number
>
;
export
async
function
readSummaryMetrics
(
report
Body
:
Locator
,
report
:
Locator
|
Frame
Locator
,
labels
:
readonly
string
[],
):
Promise
<
ParsedSummary
>
{
const
values
=
await
reportBody
.
evaluate
((
_
,
metricLabels
)
=>
{
const
text
=
document
.
body
.
innerText
??
''
;
const
compact
=
text
.
replace
(
/
\s
+/g
,
'
'
);
const
read
=
(
label
:
string
)
=>
{
const
pattern
=
new
RegExp
(
`
${
label
}
\\s*[::]?\\s*([\\d,.]+|-)`
);
const
match
=
compact
.
match
(
pattern
);
if
(
!
match
)
return
NaN
;
const
raw
=
match
[
1
].
trim
();
if
(
!
raw
||
raw
===
'
-
'
||
raw
===
'
—
'
)
return
0
;
const
num
=
parseFloat
(
raw
.
replace
(
/,/g
,
''
));
return
Number
.
isFinite
(
num
)
?
num
:
NaN
;
};
return
metricLabels
.
map
((
label
)
=>
read
(
label
));
},
[...
labels
]);
const
footerText
=
await
readReportFooterText
(
report
,
labels
);
const
summary
:
ParsedSummary
=
{};
for
(
const
[
index
,
label
]
of
labels
.
entries
())
{
expect
(
Number
.
isFinite
(
values
[
index
]),
`汇总指标「
${
label
}
」无法解析`
).
toBeTruthy
();
summary
[
label
]
=
values
[
index
];
for
(
const
label
of
labels
)
{
const
value
=
parseSummaryMetricFromText
(
footerText
,
label
);
expect
(
Number
.
isFinite
(
value
),
`汇总指标「
${
label
}
」无法解析`
).
toBeTruthy
();
summary
[
label
]
=
value
;
}
return
summary
;
}
...
...
@@ -124,8 +116,8 @@ export function assertSummaryMatchesAggregation(
return
;
}
expect
(
inboundAmount
).
toBeGreaterThanOrEqual
(
0
);
expect
(
outboundAmount
).
toBeGreaterThanOrEqual
(
0
);
expect
(
Number
.
isFinite
(
inboundAmount
),
`
${
profile
.
inboundAmountLabel
}
无法解析`
).
toBeTruthy
(
);
expect
(
Number
.
isFinite
(
outboundAmount
),
`
${
profile
.
outboundAmountLabel
}
无法解析`
).
toBeTruthy
(
);
return
;
}
...
...
@@ -147,7 +139,7 @@ export function assertSummaryMatchesAggregation(
export
type
ReportSummaryMatchDeps
=
{
profile
:
SummaryMatchProfile
;
reportBody
:
Locator
;
reportBody
:
Locator
|
FrameLocator
;
dataRows
:
Locator
;
paginationTotal
:
Locator
;
sampleRowIndex
:
number
;
...
...
@@ -245,9 +237,9 @@ export class ReportSummaryMatchControls {
}
if
(
total
>
visibleRows
)
{
await
test
.
step
(
'
跨页结果:校验底部汇总指标可读
且非负
'
,
async
()
=>
{
await
test
.
step
(
'
跨页结果:校验底部汇总指标可读
'
,
async
()
=>
{
for
(
const
label
of
profile
.
metricLabels
)
{
expect
(
summary
[
label
],
`跨页时汇总「
${
label
}
」应可解析`
).
toBe
GreaterThanOrEqual
(
0
);
expect
(
Number
.
isFinite
(
summary
[
label
]
)
,
`跨页时汇总「
${
label
}
」应可解析`
).
toBe
Truthy
(
);
}
if
(
profile
.
varietyLabel
&&
profile
.
varietyLabel
in
summary
)
{
expect
(
summary
[
profile
.
varietyLabel
]).
toBeLessThanOrEqual
(
total
);
...
...
pages/spd/helpers/report-summary.helper.ts
View file @
79a08ff4
import
type
{
FrameLocator
,
Locator
}
from
'
@playwright/test
'
;
/** 解析报表汇总/数量文本(支持逗号、负号、空、「-」)。 */
export
function
parseReportNumber
(
raw
:
string
|
undefined
|
null
):
number
{
const
text
=
raw
?.
trim
()
??
''
;
...
...
@@ -30,14 +32,66 @@ export type SummaryMetricDef = {
label
:
string
;
};
/** 从 iframe 文本中解析「标签:数值」或「标签 数值」格式汇总项。 */
export
const
SUMMARY_FOOTER_CSS
=
'
span.margin-left-8, .summary-footer, .report-summary, .ant-table-summary, .ant-table-footer, .table-summary
'
;
type
ReportRoot
=
Locator
|
FrameLocator
;
const
SUMMARY_METRIC_VALUE
=
'
([+
\\
-−–]?[
\\
d,]+(?:
\\
.[
\\
d]+)?|—|-)
'
;
/**
* 从 iframe 文本中解析「标签:数值」或「标签 数值」。
* 支持负数;左侧边界避免「数量」误匹配「选中总数量」;取最后一次命中(底部合计)。
*/
export
function
parseSummaryMetricFromText
(
bodyText
:
string
,
label
:
string
):
number
{
const
compact
=
bodyText
.
replace
(
/
\s
+/g
,
'
'
);
const
escaped
=
label
.
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
'
\\
$&
'
);
const
pattern
=
new
RegExp
(
`
${
escaped
}
\\s*[=::]?\\s*([+\\-−–]?[\\d,]+(?:\\.[\\d]+)?|—|-)`
);
const
match
=
compact
.
match
(
pattern
);
if
(
!
match
)
{
const
pattern
=
new
RegExp
(
`(?<![\\u4e00-\\u9fa5A-Za-z0-9_])
${
escaped
}
\\s*[=::]?\\s*
${
SUMMARY_METRIC_VALUE
}
`
,
'
g
'
,
);
let
last
:
string
|
undefined
;
for
(
const
match
of
compact
.
matchAll
(
pattern
))
{
last
=
match
[
1
];
}
if
(
last
===
undefined
)
{
return
NaN
;
}
return
parseReportNumber
(
match
[
1
]);
return
parseReportNumber
(
last
);
}
/** 优先读底部合计条,找不到再回退整页 body(同样走负数解析)。 */
export
async
function
readReportFooterText
(
report
:
ReportRoot
,
keywords
:
readonly
string
[]
=
[],
):
Promise
<
string
>
{
const
pattern
=
keywords
.
length
>
0
?
new
RegExp
(
keywords
.
map
((
k
)
=>
k
.
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
'
\\
$&
'
)).
join
(
'
|
'
))
:
/合计|总计/
;
const
chips
=
report
.
locator
(
SUMMARY_FOOTER_CSS
).
filter
({
hasText
:
pattern
});
const
parts
=
await
chips
.
allInnerTexts
().
catch
(()
=>
[]
as
string
[]);
const
joined
=
parts
.
map
((
text
)
=>
text
.
replace
(
/
\s
+/g
,
'
'
).
trim
()).
filter
(
Boolean
).
join
(
'
'
);
if
(
joined
&&
(
pattern
.
test
(
joined
)
||
/合计|总计/
.
test
(
joined
)))
{
return
joined
;
}
const
bodyText
=
(
await
report
.
locator
(
'
body
'
).
innerText
().
catch
(()
=>
''
)).
replace
(
/
\s
+/g
,
'
'
);
if
(
bodyText
.
trim
())
{
return
bodyText
;
}
if
(
'
innerText
'
in
report
)
{
return
((
await
(
report
as
Locator
).
innerText
().
catch
(()
=>
''
))
??
''
).
replace
(
/
\s
+/g
,
'
'
);
}
return
joined
;
}
export
function
readSummaryMetricsFromText
(
text
:
string
,
labels
:
readonly
string
[],
):
Record
<
string
,
number
>
{
const
summary
:
Record
<
string
,
number
>
=
{};
for
(
const
label
of
labels
)
{
summary
[
label
]
=
parseSummaryMetricFromText
(
text
,
label
);
}
return
summary
;
}
pages/spd/helpers/table-scroll-layout.helper.ts
View file @
79a08ff4
...
...
@@ -126,8 +126,8 @@ export class TableScrollLayoutControls {
const
result
=
await
this
.
tableWrapper
().
evaluate
((
wrapper
)
=>
{
const
MAX_GAP
=
48
;
const
MIN_BLANK_COL_WIDTH
=
20
;
// ant 表头不含纵向滚动条、固定列阴影,left/width 常见 15–
24
px 偏差;再大则是配对错列
const
MAX_ALIGN_DELTA
=
2
4
;
// ant 表头不含纵向滚动条、固定列阴影,left/width 常见 15–
50
px 偏差;再大则是配对错列
const
MAX_ALIGN_DELTA
=
3
2
;
const
issues
:
string
[]
=
[];
const
contentTable
=
...
...
@@ -289,8 +289,9 @@ export class TableScrollLayoutControls {
const
leftDelta
=
Math
.
abs
(
thRect
.
left
-
tdRect
.
left
);
const
widthDelta
=
Math
.
abs
(
thRect
.
width
-
tdRect
.
width
);
const
isActionCol
=
title
===
'
操作
'
||
title
.
includes
(
'
操作
'
);
// 操作列 / 固定右列:只看 left;其余列 left 与 width 均校验
const
alignFail
=
isActionCol
const
skipWidth
=
/ID|编码|序号/
.
test
(
title
);
// 操作列 / 证件号类:只看 left;其余列 left 与 width 均校验
const
alignFail
=
isActionCol
||
skipWidth
?
leftDelta
>
MAX_ALIGN_DELTA
:
leftDelta
>
MAX_ALIGN_DELTA
||
widthDelta
>
MAX_ALIGN_DELTA
*
2
;
if
(
alignFail
)
{
...
...
pages/spd/recon-cost-report-shared.page.ts
View file @
79a08ff4
...
...
@@ -9,6 +9,7 @@ import {
expectNumbersClose
,
parseReportNumber
,
parseSummaryMetricFromText
,
readReportFooterText
,
}
from
'
./helpers/report-summary.helper
'
;
import
{
DeptReportSharedPage
}
from
'
./dept-report-shared.page
'
;
import
{
executeExportReport
}
from
'
./helpers/export-report.helper
'
;
...
...
@@ -102,7 +103,10 @@ export class ReconCostReportSharedPage extends DeptReportSharedPage {
expect
(
await
this
.
locators
.
dataRows
.
count
(),
`按「
${
requiredValue
}
」收窄后主表无数据`
).
toBeGreaterThan
(
0
);
await
this
.
prepareExportRowSelection
();
await
this
.
assertVisibleRowsMatchFooter
(
cfg
.
metrics
,
{
absAll
:
cfg
.
absAll
});
await
this
.
assertVisibleRowsMatchFooter
(
cfg
.
metrics
,
{
absAll
:
cfg
.
absAll
,
rowIndexes
:
[
rowIndex
],
});
}
/**
...
...
@@ -113,17 +117,24 @@ export class ReconCostReportSharedPage extends DeptReportSharedPage {
options
?:
{
absAll
?:
boolean
;
readMetric
?:
(
labels
:
readonly
string
[])
=>
Promise
<
number
>
;
/** 只对照这些行(勾选首行小计);不配则对照当前页全部可见行 */
rowIndexes
?:
readonly
number
[];
},
):
Promise
<
void
>
{
const
visibleRows
=
await
this
.
locators
.
dataRows
.
count
();
expect
(
visibleRows
,
'
收窄后主表无数据
'
).
toBeGreaterThan
(
0
);
const
total
=
await
this
.
readPaginationTotalCount
().
catch
(()
=>
visibleRows
);
if
(
total
>
visibleRows
)
{
await
this
.
skipWhenDataInsufficient
(
`收窄后共
${
total
}
条且跨页,当前页无法完整核对底部汇总`
);
const
indexes
=
options
?.
rowIndexes
??
Array
.
from
({
length
:
visibleRows
},
(
_
,
i
)
=>
i
);
if
(
!
options
?.
rowIndexes
)
{
const
total
=
await
this
.
readPaginationTotalCount
().
catch
(()
=>
visibleRows
);
if
(
total
>
visibleRows
)
{
await
this
.
skipWhenDataInsufficient
(
`收窄后共
${
total
}
条且跨页,当前页无法完整核对底部汇总`
);
}
}
const
rows
:
SampleRow
[]
=
[];
for
(
let
i
=
0
;
i
<
visibleRows
;
i
+=
1
)
{
for
(
const
i
of
indexes
)
{
expect
(
i
,
'
对照行索引超出当前页
'
).
toBeLessThan
(
visibleRows
);
rows
.
push
(
await
this
.
extractSampleRow
(
i
));
}
...
...
@@ -202,19 +213,7 @@ export class ReconCostReportSharedPage extends DeptReportSharedPage {
private
async
readFirstRowFooterText
():
Promise
<
string
>
{
const
cfg
=
this
.
data
.
firstRowSummaryMatch
;
const
keywords
=
cfg
?.
footerKeywords
??
this
.
data
.
summaryKeywords
??
cfg
?.
metrics
.
map
((
m
)
=>
m
.
label
)
??
[];
const
pattern
=
keywords
.
length
>
0
?
new
RegExp
(
keywords
.
map
((
k
)
=>
k
.
replace
(
/
[
.*+?^${}()|[
\]\\]
/g
,
'
\\
$&
'
)).
join
(
'
|
'
))
:
/合计/
;
const
chips
=
this
.
locators
.
report
.
locator
(
'
span.margin-left-8, .summary-footer, .report-summary, .ant-table-summary
'
)
.
filter
({
hasText
:
pattern
});
const
parts
=
await
chips
.
allInnerTexts
();
const
joined
=
parts
.
map
((
text
)
=>
text
.
replace
(
/
\s
+/g
,
'
'
).
trim
()).
filter
(
Boolean
).
join
(
'
'
);
if
(
pattern
.
test
(
joined
)
||
/合计/
.
test
(
joined
))
{
return
joined
;
}
return
(
await
this
.
locators
.
report
.
locator
(
'
body
'
).
innerText
()).
replace
(
/
\s
+/g
,
'
'
);
return
readReportFooterText
(
this
.
locators
.
report
,
keywords
);
}
/** CSV 008:按 data.mockFieldLabel + mockNotExistProduct 反例查询(单次写入并查询,不跨页探测)。 */
...
...
testcase/统计报表-中心库出入库报表-净出库汇总查询(多维度)-UI自动化测试用例-01.csv
View file @
79a08ff4
...
...
@@ -50,50 +50,46 @@ STAT-NOS-015,统计报表·中心库出入库报表,净出库汇总查询·主
2. 横向滚动使「产品名称」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示,
STAT-NOS-016,统计报表·中心库出入库报表,净出库汇总查询·主表列表,规格型号列升序降序切换,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;至少 2 条数据,"1. 默认条件点击「查 询」
2. 横向滚动使「规格型号」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示,
STAT-NOS-017,统计报表·中心库出入库报表,净出库汇总查询·主表列表,单位列升序降序切换,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;至少 2 条数据,"1. 默认条件点击「查 询」
STAT-NOS-016,统计报表·中心库出入库报表,净出库汇总查询·主表列表,单位列升序降序切换,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;至少 2 条数据,"1. 默认条件点击「查 询」
2. 横向滚动使「单位」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示,
STAT-NOS-01
8
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,耗材类型列升序降序切换,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;至少 2 条数据,"1. 默认条件点击「查 询」
STAT-NOS-01
7
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,耗材类型列升序降序切换,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;至少 2 条数据,"1. 默认条件点击「查 询」
2. 横向滚动使「耗材类型」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示,
STAT-NOS-01
9
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,厂家列升序降序切换,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;至少 2 条数据,"1. 默认条件点击「查 询」
STAT-NOS-01
8
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,厂家列升序降序切换,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;至少 2 条数据,"1. 默认条件点击「查 询」
2. 横向滚动使「厂家」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示,
STAT-NOS-0
20
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,27位医保编码列升序降序切换,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;至少 2 条数据,"1. 默认条件点击「查 询」
STAT-NOS-0
19
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,27位医保编码列升序降序切换,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;至少 2 条数据,"1. 默认条件点击「查 询」
2. 横向滚动使「27位医保编码」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示,
STAT-NOS-02
1
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,数量列升序降序切换,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;至少 2 条数据,"1. 默认条件点击「查 询」
STAT-NOS-02
0
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,数量列升序降序切换,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;至少 2 条数据,"1. 默认条件点击「查 询」
2. 横向滚动使「数量」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示,
STAT-NOS-02
2
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,金额列升序降序切换,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;至少 2 条数据,"1. 默认条件点击「查 询」
STAT-NOS-02
1
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,金额列升序降序切换,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;至少 2 条数据,"1. 默认条件点击「查 询」
2. 横向滚动使「金额」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示,
STAT-NOS-02
3
,统计报表·中心库出入库报表,净出库汇总查询·分页,有数据时展示分页总条数,高,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;已默认查询有数据,"1. 默认查询
STAT-NOS-02
2
,统计报表·中心库出入库报表,净出库汇总查询·分页,有数据时展示分页总条数,高,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;已默认查询有数据,"1. 默认查询
2. 查看分页",显示「共 N 条」,
STAT-NOS-02
4
,统计报表·中心库出入库报表,净出库汇总查询·分页,切换至第2页,高,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;超过 1 页,1. 点击页码 2,第 2 页正确;列表无数据或不足2页时 skip。,
STAT-NOS-02
5
,统计报表·中心库出入库报表,净出库汇总查询·分页,切换每页条数,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;有多页,1. 切换每页条数,行数与档位一致;列表无数据时 skip。,
STAT-NOS-02
6
,统计报表·中心库出入库报表,净出库汇总查询·导出,按当前条件导出文件,高,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;含维度查询有数据,"1. 默认条件点击「查 询」
STAT-NOS-02
3
,统计报表·中心库出入库报表,净出库汇总查询·分页,切换至第2页,高,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;超过 1 页,1. 点击页码 2,第 2 页正确;列表无数据或不足2页时 skip。,
STAT-NOS-02
4
,统计报表·中心库出入库报表,净出库汇总查询·分页,切换每页条数,中,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;有多页,1. 切换每页条数,行数与档位一致;列表无数据时 skip。,
STAT-NOS-02
5
,统计报表·中心库出入库报表,净出库汇总查询·导出,按当前条件导出文件,高,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;含维度查询有数据,"1. 默认条件点击「查 询」
2. 点击「导 出」
3. 检查无报错信息,页面、接口、系统正常,无系统异常",点击导出按钮后,无报错信息,页面、接口、系统正常,无系统异常。,
STAT-NOS-02
7
,统计报表·中心库出入库报表,净出库汇总查询·高级筛选抽屉,快捷区条件自动带入高级筛选,高,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;已默认查询至少有 1 条数据,"1. 默认查询并从列表首行读取样本(物资编码/物资名称/单据日期)
STAT-NOS-02
6
,统计报表·中心库出入库报表,净出库汇总查询·高级筛选抽屉,快捷区条件自动带入高级筛选,高,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;已默认查询至少有 1 条数据,"1. 默认查询并从列表首行读取样本(物资编码/物资名称/单据日期)
2. 将样本对应条件填入快捷区
3. 点击「查 询」
4. 打开高级筛选抽屉(不手动修改抽屉字段)
5. 检查抽屉内同名字段值与快捷区一致",抽屉「物资编码」「物资名称」「单据日期」与快捷区已填值一致;无需重复录入,
STAT-NOS-02
8
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,打开列设置面板,低,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;已默认查询有数据,"1. 默认查询
STAT-NOS-02
7
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,打开列设置面板,低,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;已默认查询有数据,"1. 默认查询
2. 点击表格右上角列设置齿轮
3. 检查面板",列设置面板可见;含「列展示」「字段设置」;字段列表含可勾选列,
STAT-NOS-02
9
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,列设置取消勾选后再勾选列表列恢复,低,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;已默认查询有数据;已确认目标非核心列默认显示,"1. 打开列设置面板
STAT-NOS-02
8
,统计报表·中心库出入库报表,净出库汇总查询·主表列表,列设置取消勾选后再勾选列表列恢复,低,测试环境可访问;测试账号已登录并进入净出库汇总查询(多维度)页(iframe centerPages/netOutSummary);勿误点出库汇总查询;已默认查询有数据;已确认目标非核心列默认显示,"1. 打开列设置面板
2. 取消勾选「27位医保编码」(若无则选「HIS收费编码」「规格型号」等非核心列)
3. 关闭面板
4. 检查表头不再显示该列
...
...
testcase/统计报表-结算成本报表-产品成本排名-UI自动化测试用例-01.csv
View file @
79a08ff4
...
...
@@ -8,15 +8,15 @@ STAT-CPR-002,统计报表·结算成本报表,产品成本排名·高级筛选
STAT-CPR-003,统计报表·结算成本报表,产品成本排名·高级筛选抽屉,抽屉内重置恢复抽屉字段默认,中,测试环境可访问;测试账号已登录并进入产品成本排名页(iframe settle/productRank);已打开高级筛选抽屉,"1. 在抽屉修改任一文本/下拉字段
2. 点击抽屉底部「重 置」
3. 检查抽屉字段与快捷区",抽屉字段恢复默认;快捷区条件不被清空。,
STAT-CPR-004,统计报表·结算成本报表,产品成本排名·高级筛选抽屉,全量查询条件组合查询,高,测试环境可访问;测试账号已登录并进入产品成本排名页(iframe settle/productRank),"1. 快捷区各字段(统计月份、院区
、耗材类型、耗材分类
)分别从可选值中随机选取
2. 打开高级筛选,抽屉内各字段(是否重点监管等)分别从可选值中随机选取
STAT-CPR-004,统计报表·结算成本报表,产品成本排名·高级筛选抽屉,全量查询条件组合查询,高,测试环境可访问;测试账号已登录并进入产品成本排名页(iframe settle/productRank),"1. 快捷区各字段(统计月份、院区)分别从可选值中随机选取
2. 打开高级筛选,抽屉内各字段(
耗材类型、耗材分类、
是否重点监管等)分别从可选值中随机选取
3. 点击「筛 选」并等待刷新
4. 检查页面无报错、无未捕获异常",查询正常无报错;页面、接口、系统正常,无系统异常。,
STAT-CPR-005,统计报表·结算成本报表,产品成本排名·高级筛选抽屉,快捷区条件自动带入高级筛选,高,测试环境可访问;测试账号已登录并进入产品成本排名页(iframe settle/productRank);已默认查询有数据,"1. searchAndExtractSample() 读取样本
2. 快捷区填入统计月份、
耗材类型
2. 快捷区填入统计月份、
院区
3. 点击「查 询」
4. 打开高级筛选(不手动修改抽屉)
5. 校验抽屉同名字段与快捷区一致",抽屉「统计月份」「
耗材类型
」与快捷区已填值一致,
5. 校验抽屉同名字段与快捷区一致",抽屉「统计月份」「
院区
」与快捷区已填值一致,
STAT-CPR-006,统计报表·结算成本报表,产品成本排名·汇总维度与选项勾选,双维度均不勾选查询,高,测试环境可访问;测试账号已登录并进入产品成本排名页(iframe settle/productRank),"1. 同时取消勾选「显示注册人」「显示品规」
2. 点击「查 询」并等待刷新
3. 检查页面无报错、无未捕获异常",查询正常无报错;页面、接口、系统正常,无系统异常。,
...
...
testcase/统计报表-结算成本报表-财务产品进销存-UI自动化测试用例-01.csv
View file @
79a08ff4
...
...
@@ -68,47 +68,43 @@ STAT-CFP-018,统计报表·结算成本报表,财务产品进销存·列排序,
2. 必要时横向滚动使「产品编号」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示。,
STAT-CFP-019,统计报表·结算成本报表,财务产品进销存·列排序,生产厂家列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
2. 必要时横向滚动使「生产厂家」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示。,
STAT-CFP-020,统计报表·结算成本报表,财务产品进销存·列排序,耗材类型列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
STAT-CFP-019,统计报表·结算成本报表,财务产品进销存·列排序,耗材类型列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
2. 必要时横向滚动使「耗材类型」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示。,
STAT-CFP-02
1
,统计报表·结算成本报表,财务产品进销存·列排序,单位列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
STAT-CFP-02
0
,统计报表·结算成本报表,财务产品进销存·列排序,单位列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
2. 必要时横向滚动使「单位」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示。,
STAT-CFP-02
2
,统计报表·结算成本报表,财务产品进销存·列排序,期初数量列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
STAT-CFP-02
1
,统计报表·结算成本报表,财务产品进销存·列排序,期初数量列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
2. 必要时横向滚动使「期初数量」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示。,
STAT-CFP-02
3
,统计报表·结算成本报表,财务产品进销存·列排序,入库数量列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
STAT-CFP-02
2
,统计报表·结算成本报表,财务产品进销存·列排序,入库数量列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
2. 必要时横向滚动使「入库数量」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示。,
STAT-CFP-02
4
,统计报表·结算成本报表,财务产品进销存·列排序,入库金额列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
STAT-CFP-02
3
,统计报表·结算成本报表,财务产品进销存·列排序,入库金额列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
2. 必要时横向滚动使「入库金额」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示。,
STAT-CFP-02
5
,统计报表·结算成本报表,财务产品进销存·列排序,出库数量列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
STAT-CFP-02
4
,统计报表·结算成本报表,财务产品进销存·列排序,出库数量列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
2. 必要时横向滚动使「出库数量」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示。,
STAT-CFP-02
6
,统计报表·结算成本报表,财务产品进销存·列排序,出库金额列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
STAT-CFP-02
5
,统计报表·结算成本报表,财务产品进销存·列排序,出库金额列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
2. 必要时横向滚动使「出库金额」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示。,
STAT-CFP-02
7
,统计报表·结算成本报表,财务产品进销存·列排序,报损数量列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
STAT-CFP-02
6
,统计报表·结算成本报表,财务产品进销存·列排序,报损数量列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
2. 必要时横向滚动使「报损数量」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示。,
STAT-CFP-02
8
,统计报表·结算成本报表,财务产品进销存·列排序,报损金额列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
STAT-CFP-02
7
,统计报表·结算成本报表,财务产品进销存·列排序,报损金额列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
2. 必要时横向滚动使「报损金额」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示。,
STAT-CFP-02
9
,统计报表·结算成本报表,财务产品进销存·列排序,期末数量列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
STAT-CFP-02
8
,统计报表·结算成本报表,财务产品进销存·列排序,期末数量列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务产品进销存页(iframe settel/financeWithProduct);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
2. 必要时横向滚动使「期末数量」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
4. 第二次点击该列 .ant-table-column-sorter → 校验降序指示器(▼高亮或 aria-sort=descending)",排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示。,
testcase/统计报表-结算成本报表-财务入库单-UI自动化测试用例-01.csv
View file @
79a08ff4
...
...
@@ -25,8 +25,9 @@ STAT-CFI-008,统计报表·结算成本报表,财务入库单·分页,切换每
2. 切换每页条数(如 100 条/页)
3. 统计当前页可见行数",单页行数与档位一致;列表无数据时 skip。,
STAT-CFI-009,统计报表·结算成本报表,财务入库单·打印,按当前查询条件打印,高,测试环境可访问;测试账号已登录并进入财务入库单页(iframe settle/inStock);已默认查询有数据,"1. 默认条件查询
2. 点击工具栏「打 印」
3. 观察系统响应(打印对话框/预览/toast)",打印流程可启动(打印对话框/预览/toast);无 error 提示;无未捕获异常。,
2. 勾选列表明细行
3. 确认工具栏「打 印」可点击后点击
4. 观察系统响应(打印对话框/预览/toast)",打印按钮在勾选明细后可点击;打印流程可启动(打印对话框/预览/toast);无 error 提示;无未捕获异常。,
STAT-CFI-010,统计报表·结算成本报表,财务入库单·列排序,进院供应商名称列升序降序切换,中,测试环境可访问;测试账号已登录并进入财务入库单页(iframe settle/inStock);已默认查询且主表至少有 2 条数据,"1. 默认条件查询
2. 必要时横向滚动使「进院供应商名称」列表头可见
3. 第一次点击该列 .ant-table-column-sorter → 校验升序指示器(▲高亮或 aria-sort=ascending)
...
...
testcases/统计报表-中心库出入库报表-净出库汇总查询(多维度)-UI自动化测试用例-01.md
View file @
79a08ff4
...
...
@@ -67,7 +67,7 @@
##### 【P1】【TC-UI-STAT-NOS-006】【TP_STAT_NOS_036】抽屉内重置
##### 【P0】【TC-UI-STAT-NOS-02
7
】【TP_STAT_NOS_037】快捷区条件自动带入高级筛选
##### 【P0】【TC-UI-STAT-NOS-02
6
】【TP_STAT_NOS_037】快捷区条件自动带入高级筛选
-
操作步骤(可选):
1.
默认查询,
`searchAndExtractSample()`
从首行读取样本。
...
...
@@ -108,19 +108,19 @@
核心列可见;横向拖拉后列表可正常左右滚动;远侧「数量/金额」列可见;表头相邻列无变形空格/重叠;表头与数据列对齐正常。
##### 【P1】【TC-UI-STAT-NOS-009~02
2
】【TP_STAT_NOS_043】各可排序列升序降序切换
##### 【P1】【TC-UI-STAT-NOS-009~02
1
】【TP_STAT_NOS_043】各可排序列升序降序切换
页面默认可排序列(
2026-08-31 页面表头核验
共
**1
4
**
列;
**医保单件名称、HIS收费编码**
无 sorter);自动化见
`tests/spd/center-report-net-out.spec.ts`
列排序块。
页面默认可排序列(共
**1
3
**
列;
**医保单件名称、HIS收费编码**
无 sorter
;
**规格型号**
列排序已删除
);自动化见
`tests/spd/center-report-net-out.spec.ts`
列排序块。
-
操作步骤(可选):默认查询 →
`scrollColumnIntoView(column)`
→
`expectColumnSortToggle(column)`
(先升序后降序各校验一次)。
可排序列:院区、上级科室编码、上级科室名称、科室编码、科室名称、产品编码、产品名称、
规格型号、
单位、耗材类型、厂家、27位医保编码、数量、金额。
可排序列:院区、上级科室编码、上级科室名称、科室编码、科室名称、产品编码、产品名称、单位、耗材类型、厂家、27位医保编码、数量、金额。
###### 预期结果
各可排序列均支持升序/降序切换;排序指示器与当前排序方向一致。
##### 【P2】【TC-UI-STAT-NOS-02
8
~02
9
】【TP_STAT_NOS_044】列设置
##### 【P2】【TC-UI-STAT-NOS-02
7
~02
8
】【TP_STAT_NOS_044】列设置
-
操作步骤(可选):默认查询 → 点击列设置齿轮 →
`expectColumnSettingsPanel()`
。
...
...
@@ -128,7 +128,7 @@
面板含「列展示」「字段设置」「重置」;字段勾选列表可见。
##### 【P2】【TC-UI-STAT-NOS-02
9
】【TP_STAT_NOS_044】列设置取消勾选后再勾选列表列恢复
##### 【P2】【TC-UI-STAT-NOS-02
8
】【TP_STAT_NOS_044】列设置取消勾选后再勾选列表列恢复
-
操作步骤(可选):
1.
`pickToggleColumnName()`
选取非核心列(27位医保编码/HIS收费编码/规格型号)。
...
...
@@ -145,11 +145,11 @@
#### 功能验证点
##### 【P0】【TC-UI-STAT-NOS-02
3
】【TP_STAT_NOS_049】分页展示总条数
##### 【P0】【TC-UI-STAT-NOS-02
2
】【TP_STAT_NOS_049】分页展示总条数
##### 【P0】【TC-UI-STAT-NOS-02
4
】【TP_STAT_NOS_050】切换至第 2 页
##### 【P0】【TC-UI-STAT-NOS-02
3
】【TP_STAT_NOS_050】切换至第 2 页
##### 【P1】【TC-UI-STAT-NOS-02
5
】【TP_STAT_NOS_051】切换每页条数
##### 【P1】【TC-UI-STAT-NOS-02
4
】【TP_STAT_NOS_051】切换每页条数
###### 预期结果
...
...
@@ -161,7 +161,7 @@
#### 功能验证点
##### 【P0】【TC-UI-STAT-NOS-02
6
】【TP_STAT_NOS_054】按当前条件导出
##### 【P0】【TC-UI-STAT-NOS-02
5
】【TP_STAT_NOS_054】按当前条件导出
-
操作步骤:
1.
默认条件点击「查 询」
...
...
testcases/统计报表-结算成本报表-产品成本排名-UI自动化测试用例-01.md
View file @
79a08ff4
...
...
@@ -85,8 +85,8 @@
-
前置条件:测试环境可访问;测试账号已登录并进入产品成本排名页(iframe settle/productRank)
-
操作步骤:
1.
快捷区各字段(统计月份、院区
、耗材类型、耗材分类
)分别从可选值中随机选取
2.
打开高级筛选,抽屉内各字段(是否重点监管等)分别从可选值中随机选取
1.
快捷区各字段(统计月份、院区)分别从可选值中随机选取
2.
打开高级筛选,抽屉内各字段(
耗材类型、耗材分类、
是否重点监管等)分别从可选值中随机选取
3.
点击「筛 选」并等待刷新
4.
检查页面无报错、无未捕获异常
-
自动化断言:与 CSV
`STAT-CPR-004`
及
`tests/spd/cost-product-rank.spec.ts`
一致(
`searchWithRandomQuickAndDrawerCombo`
+
`expectQueryWithoutError`
)
...
...
@@ -102,7 +102,7 @@
-
前置条件:测试环境可访问;测试账号已登录并进入产品成本排名页(iframe settle/productRank);已默认查询有数据
-
操作步骤:
1.
searchAndExtractSample() 读取样本
2.
快捷区填入统计月份、
耗材类型
2.
快捷区填入统计月份、
院区
3.
点击「查 询」
4.
打开高级筛选(不手动修改抽屉)
5.
校验抽屉同名字段与快捷区一致
...
...
@@ -269,13 +269,13 @@
-
操作步骤:
1.
执行查询
2.
在左侧产品排名列表分页点击页码 2
3.
检查页码高亮与左侧首行数据
4.
点选左侧首行,确认右侧明细随之刷新
3.
检查页码高亮与左侧首行数据
,并确认右侧明细分页仍停在原页
4.
点选左侧首行,确认右侧明细随之刷新
;若右侧也有第 2 页,再切右侧第 2 页并确认左侧页码不变
-
自动化断言:与 CSV
`STAT-CPR-022`
及
`tests/spd/cost-product-rank.spec.ts`
一致
###### 预期结果
第 2 页数据正确;页码高亮;右侧明细随选中行更新;列表无数据或不足2页时 skip。
第 2 页数据正确;
左侧
页码高亮;右侧明细
分页保持独立;右侧明细
随选中行更新;列表无数据或不足2页时 skip。
---
...
...
testcases/统计报表-结算成本报表-财务产品进销存-UI自动化测试用例-01.md
View file @
79a08ff4
...
...
@@ -309,9 +309,9 @@
排序按钮可点击;升序与降序指示器分别激活;列表数据正常展示。
其余可排序列与 CSV
`STAT-CFP-017`
~
`STAT-CFP-0
30
`
一一对应:规格型号、产品编号、
生产厂家、
耗材类型、单位、期初数量、
期初金额、
入库数量、入库金额、出库数量、出库金额、报损数量、报损金额、期末数量。
其余可排序列与 CSV
`STAT-CFP-017`
~
`STAT-CFP-0
28
`
一一对应:规格型号、产品编号、耗材类型、单位、期初数量、入库数量、入库金额、出库数量、出库金额、报损数量、报损金额、期末数量。
**已删除**
:
`STAT-CFP-031`
期末金额列排序(页面无排序控件)。
**已删除**
:
`STAT-CFP-
019`
生产厂家列排序;
`STAT-CFP-
031`
期末金额列排序(页面无排序控件)。
---
...
...
Prev
1
2
3
4
5
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment